nodejs-poolcontroller 7.5.1 → 7.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  2. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  3. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  5. package/Changelog +19 -0
  6. package/Dockerfile +3 -3
  7. package/README.md +13 -8
  8. package/app.ts +1 -1
  9. package/config/Config.ts +38 -2
  10. package/config/VersionCheck.ts +27 -12
  11. package/controller/Constants.ts +2 -1
  12. package/controller/Equipment.ts +193 -9
  13. package/controller/Errors.ts +10 -0
  14. package/controller/Lockouts.ts +503 -0
  15. package/controller/State.ts +269 -64
  16. package/controller/boards/AquaLinkBoard.ts +1000 -0
  17. package/controller/boards/BoardFactory.ts +4 -0
  18. package/controller/boards/EasyTouchBoard.ts +468 -144
  19. package/controller/boards/IntelliCenterBoard.ts +466 -307
  20. package/controller/boards/IntelliTouchBoard.ts +37 -5
  21. package/controller/boards/NixieBoard.ts +671 -141
  22. package/controller/boards/SystemBoard.ts +1397 -641
  23. package/controller/comms/Comms.ts +462 -362
  24. package/controller/comms/messages/Messages.ts +174 -30
  25. package/controller/comms/messages/config/ChlorinatorMessage.ts +6 -3
  26. package/controller/comms/messages/config/CircuitMessage.ts +1 -0
  27. package/controller/comms/messages/config/ExternalMessage.ts +10 -8
  28. package/controller/comms/messages/config/HeaterMessage.ts +141 -29
  29. package/controller/comms/messages/config/OptionsMessage.ts +9 -2
  30. package/controller/comms/messages/config/PumpMessage.ts +53 -35
  31. package/controller/comms/messages/config/ScheduleMessage.ts +33 -25
  32. package/controller/comms/messages/config/ValveMessage.ts +2 -2
  33. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +38 -86
  34. package/controller/comms/messages/status/EquipmentStateMessage.ts +59 -23
  35. package/controller/comms/messages/status/HeaterStateMessage.ts +57 -3
  36. package/controller/comms/messages/status/IntelliChemStateMessage.ts +56 -8
  37. package/controller/comms/messages/status/PumpStateMessage.ts +23 -1
  38. package/controller/nixie/Nixie.ts +1 -1
  39. package/controller/nixie/bodies/Body.ts +3 -0
  40. package/controller/nixie/chemistry/ChemController.ts +164 -51
  41. package/controller/nixie/chemistry/Chlorinator.ts +137 -88
  42. package/controller/nixie/circuits/Circuit.ts +51 -19
  43. package/controller/nixie/heaters/Heater.ts +241 -31
  44. package/controller/nixie/pumps/Pump.ts +488 -206
  45. package/controller/nixie/schedules/Schedule.ts +91 -35
  46. package/controller/nixie/valves/Valve.ts +1 -1
  47. package/defaultConfig.json +20 -0
  48. package/package.json +21 -21
  49. package/web/Server.ts +94 -49
  50. package/web/bindings/aqualinkD.json +505 -0
  51. package/web/bindings/influxDB.json +71 -1
  52. package/web/bindings/mqtt.json +98 -39
  53. package/web/bindings/mqttAlt.json +59 -1
  54. package/web/interfaces/baseInterface.ts +1 -0
  55. package/web/interfaces/httpInterface.ts +23 -2
  56. package/web/interfaces/influxInterface.ts +45 -10
  57. package/web/interfaces/mqttInterface.ts +114 -54
  58. package/web/services/config/Config.ts +55 -132
  59. package/web/services/state/State.ts +81 -4
  60. package/web/services/state/StateSocket.ts +4 -4
  61. package/web/services/utilities/Utilities.ts +8 -6
  62. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  63. package/config copy.json +0 -300
  64. package/issue_template.md +0 -52
@@ -70,6 +70,7 @@ interface IPoolSystem {
70
70
 
71
71
  export class PoolSystem implements IPoolSystem {
72
72
  public _hasChanged: boolean = false;
73
+ public isReady: boolean = false;
73
74
  constructor() {
74
75
  this.cfgPath = path.posix.join(process.cwd(), '/data/poolConfig.json');
75
76
  }
@@ -142,6 +143,7 @@ export class PoolSystem implements IPoolSystem {
142
143
  }
143
144
  else
144
145
  this.initNixieController();
146
+ this.isReady = true;
145
147
  }
146
148
  public init() {
147
149
  let cfg = this.loadConfigFile(this.cfgPath, {});
@@ -202,7 +204,7 @@ export class PoolSystem implements IPoolSystem {
202
204
  }
203
205
 
204
206
  public resetSystem() {
205
- conn.pause();
207
+ conn.pauseAll();
206
208
  this.resetData();
207
209
  state.resetData();
208
210
  this.data.controllerType === 'unknown';
@@ -210,7 +212,7 @@ export class PoolSystem implements IPoolSystem {
210
212
  this.controllerType = ControllerType.Unknown;
211
213
  state.status = 0;
212
214
  this.board = BoardFactory.fromControllerType(ControllerType.Unknown, this);
213
- setTimeout(function () { state.status = 0; conn.resume(); }, 0);
215
+ setTimeout(function () { state.status = 0; conn.resumeAll(); }, 0);
214
216
  }
215
217
  public get controllerType(): ControllerType { return this.data.controllerType as ControllerType; }
216
218
  public set controllerType(val: ControllerType) {
@@ -537,7 +539,7 @@ class EqItemCollection<T> implements IEqItemCollection {
537
539
  return this.createItem(extend({}, { id: ndx + 1 }, data));
538
540
  }
539
541
  public getItemById(id: number | string, add?: boolean, data?: any): T {
540
- let itm = this.find(elem => elem.id === id && typeof elem.id !== 'undefined');
542
+ let itm = this.find(elem => { return typeof (elem as { id?}).id !== 'undefined' && (elem as { id?}).id === id });
541
543
  if (typeof itm !== 'undefined') return itm;
542
544
  if (typeof add !== 'undefined' && add) return this.add(extend(true, { id: id }, data));
543
545
  return this.createItem(data || { id: id });
@@ -567,7 +569,7 @@ class EqItemCollection<T> implements IEqItemCollection {
567
569
  this.data.splice(ndx, 1);
568
570
  }
569
571
  // Finds an item and returns undefined if it doesn't exist.
570
- public find(f: (value: any, index?: number, obj?: any) => boolean): T {
572
+ public find(f: (value: T, index?: number, obj?: any) => boolean): T {
571
573
  let itm = this.data.find(f);
572
574
  if (typeof itm !== 'undefined') return this.createItem(itm);
573
575
  }
@@ -612,7 +614,7 @@ class EqItemCollection<T> implements IEqItemCollection {
612
614
  });
613
615
  }
614
616
  public sort(fn: (a, b) => number) { this.data.sort(fn); }
615
- public count(fn: () => boolean): number { return this.data.filter(fn).length; }
617
+ public count(fn: (value: T, index?: any, array?: any[]) => boolean): number { return this.data.filter(fn).length; }
616
618
  public getNextEquipmentId(range: EquipmentIdRange, exclude?: number[]): number {
617
619
  for (let i = range.start; i <= range.end; i++) {
618
620
  let eq = this.data.find(elem => elem.id === i);
@@ -724,6 +726,16 @@ export class Options extends EqItem {
724
726
  if (typeof this.data.clockMode === 'undefined') this.data.clockMode = 12;
725
727
  if (typeof this.data.adjustDST === 'undefined') this.data.adjustDST = true;
726
728
  if (typeof this.data.freezeThreshold === 'undefined') this.data.freezeThreshold = 35;
729
+ if (typeof this.data.pumpDelay === 'undefined') this.data.pumpDelay = false;
730
+ if (typeof this.data.valveDelayTime === 'undefined') this.data.valveDelayTime = 30;
731
+ // RKS: 12-04-21 If you are reading this in a few months delete the line below.
732
+ if (this.data.valveDelayTime > 1000) this.data.valveDelayTime = this.data.valveDelayTime / 1000;
733
+ if (typeof this.data.heaterStartDelay === 'undefined') this.data.heaterStartDelay = true;
734
+ if (typeof this.data.cleanerStartDelay === 'undefined') this.data.cleanerStartDelay = true;
735
+ if (typeof this.data.cleanerSolarDelay === 'undefined') this.data.cleanerSolarDelay = true;
736
+ if (typeof this.data.heaterStartDelayTime === 'undefined') this.data.heaterStartDelayTime = 10;
737
+ if (typeof this.data.cleanerStartDelayTime === 'undefined') this.data.cleanerStartDelayTime = 300; // 5min
738
+ if (typeof this.data.cleanerSolarDelayTime === 'undefined') this.data.cleanerSolarDelayTime = 300; // 5min
727
739
  }
728
740
  public get clockMode(): number | any { return this.data.clockMode; }
729
741
  public set clockMode(val: number | any) { this.setDataVal('clockMode', sys.board.valueMaps.clockModes.encode(val)); }
@@ -740,10 +752,25 @@ export class Options extends EqItem {
740
752
  public set manualHeat(val: boolean) { this.setDataVal('manualHeat', val); }
741
753
  public get pumpDelay(): boolean { return this.data.pumpDelay; }
742
754
  public set pumpDelay(val: boolean) { this.setDataVal('pumpDelay', val); }
755
+ public get valveDelayTime(): number { return this.data.valveDelayTime; }
756
+ public set valveDelayTime(val: number) { this.setDataVal('valveDelayTime', val); }
743
757
  public get cooldownDelay(): boolean { return this.data.cooldownDelay; }
744
758
  public set cooldownDelay(val: boolean) { this.setDataVal('cooldownDelay', val); }
745
759
  public get freezeThreshold(): number { return this.data.freezeThreshold; }
746
760
  public set freezeThreshold(val: number) { this.setDataVal('freezeThreshold', val); }
761
+ public get heaterStartDelay(): boolean { return this.data.heaterStartDelay; }
762
+ public set heaterStartDelay(val: boolean) { this.setDataVal('heaterStartDelay', val); }
763
+ public get heaterStartDelayTime(): number { return this.data.heaterStartDelayTime; }
764
+ public set heaterStartDelayTime(val: number) { this.setDataVal('heaterStartDelayTime', val); }
765
+
766
+ public get cleanerStartDelay(): boolean { return this.data.cleanerStartDelay; }
767
+ public set cleanerStartDelay(val: boolean) { this.setDataVal('cleanerStartDelay', val); }
768
+ public get cleanerStartDelayTime(): number { return this.data.cleanerStartDelayTime; }
769
+ public set cleanerStartDelayTime(val: number) { this.setDataVal('cleanerStartDelayTime', val); }
770
+ public get cleanerSolarDelay(): boolean { return this.data.cleanerSolarDelay; }
771
+ public set cleanerSolarDelay(val: boolean) { this.setDataVal('cleanerSolarDelay', val); }
772
+ public get cleanerSolarDelayTime(): number { return this.data.cleanerSolarDelayTime; }
773
+ public set cleanerSolarDelayTime(val: number) { this.setDataVal('cleanerSolarDelayTime', val); }
747
774
 
748
775
  //public get airTempAdj(): number { return typeof this.data.airTempAdj === 'undefined' ? 0 : this.data.airTempAdj; }
749
776
  //public set airTempAdj(val: number) { this.setDataVal('airTempAdj', val); }
@@ -994,7 +1021,7 @@ export class BodyCollection extends EqItemCollection<Body> {
994
1021
  let body = this.find(elem => {
995
1022
  if (typeof obj.id !== 'undefined') return obj.id === elem.id;
996
1023
  else if (typeof obj.circuit !== 'undefined') return obj.circuit === elem.circuit;
997
- else if (typeof obj.name !== 'undefined') return obj.name === body.name;
1024
+ else if (typeof obj.name !== 'undefined') return obj.name === elem.name;
998
1025
  else return false;
999
1026
  });
1000
1027
  return body;
@@ -1004,6 +1031,7 @@ export class Body extends EqItem {
1004
1031
  public dataName = 'bodyConfig';
1005
1032
  public initData() {
1006
1033
  if (typeof this.data.capacityUnits === 'undefined') this.data.capacityUnits = 1;
1034
+ if (typeof this.data.showInDashboard === 'undefined') this.data.showInDashboard = true;
1007
1035
  }
1008
1036
  public get id(): number { return this.data.id; }
1009
1037
  public set id(val: number) { this.data.id = val; }
@@ -1029,6 +1057,8 @@ export class Body extends EqItem {
1029
1057
  public set heatSetpoint(val: number) { this.setDataVal('setPoint', val); }
1030
1058
  public get coolSetpoint(): number { return this.data.coolSetpoint; }
1031
1059
  public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
1060
+ public get showInDashboard(): boolean { return this.data.showInDashboard; }
1061
+ public set showInDashboard(val: boolean) { this.setDataVal('showInDashboard', val); }
1032
1062
  public get capacityUnits(): number | any { return this.data.capacityUnits; }
1033
1063
  public set capacityUnits(val: number | any) { this.setDataVal('capacityUnits', sys.board.valueMaps.volumeUnits.encode(val)); }
1034
1064
  public getHeatModes() { return sys.board.bodies.getHeatModes(this.id); }
@@ -1083,6 +1113,8 @@ export class Schedule extends EqItem {
1083
1113
  public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
1084
1114
  public get isActive(): boolean { return this.data.isActive; }
1085
1115
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1116
+ public get disabled(): boolean { return this.data.disabled; }
1117
+ public set disabled(val: boolean) { this.setDataVal('disabled', val); }
1086
1118
  public get startMonth(): number { return this._startDate.getMonth() + 1; }
1087
1119
  public set startMonth(val: number) { if (typeof this._startDate === 'undefined') this._startDate = new Date(); this._startDate.setMonth(val - 1); this._saveStartDate(); }
1088
1120
  public get startDay(): number { if (typeof this._startDate === 'undefined') this._startDate = new Date(); return this._startDate.getDate(); }
@@ -1129,6 +1161,9 @@ export class EggTimer extends EqItem {
1129
1161
  }
1130
1162
  export class CircuitCollection extends EqItemCollection<Circuit> {
1131
1163
  constructor(data: any, name?: string) { super(data, name || "circuits"); }
1164
+ public filter(f: (value: Circuit, index?: any, array?: any[]) => boolean): CircuitCollection {
1165
+ return new CircuitCollection({ circuits: this.data.filter(f) });
1166
+ }
1132
1167
  public createItem(data: any): Circuit { return new Circuit(data); }
1133
1168
  public add(obj: any): Circuit {
1134
1169
  this.data.push(obj);
@@ -1185,7 +1220,35 @@ export class Circuit extends EqItem implements ICircuit {
1185
1220
  public get deviceBinding(): string { return this.data.deviceBinding; }
1186
1221
  public set deviceBinding(val: string) { this.setDataVal('deviceBinding', val); }
1187
1222
  public get hasHeatSource() { return typeof sys.board.valueMaps.circuitFunctions.get(this.type || 0).hasHeatSource !== 'undefined' ? sys.board.valueMaps.circuitFunctions.get(this.type || 0).hasHeatSource : false };
1188
- public getLightThemes() { return sys.board.circuits.getLightThemes(this.type); }
1223
+ public getLightThemes() {
1224
+ // Lets do this universally driven by the metadata.
1225
+ let cf = sys.board.valueMaps.circuitFunctions.transform(this.type);
1226
+ if (cf.isLight && typeof cf.theme !== 'undefined') {
1227
+ let arrThemes = sys.board.valueMaps.lightThemes.toArray();
1228
+ let themes = [];
1229
+ for (let i = 0; i < arrThemes.length; i++) {
1230
+ let thm = arrThemes[i];
1231
+ if (typeof thm.types !== 'undefined' && thm.types.length > 0 && thm.types.includes(cf.theme)) themes.push(thm);
1232
+ }
1233
+ return themes;
1234
+ }
1235
+ else return [];
1236
+ }
1237
+ public getLightCommands() {
1238
+ // Lets do this universally driven by the metadata.
1239
+ let cf = sys.board.valueMaps.circuitFunctions.transform(this.type);
1240
+ if (cf.isLight && typeof cf.theme !== 'undefined') {
1241
+ let arrCommands = sys.board.valueMaps.lightCommands.toArray();
1242
+ let cmds = [];
1243
+ for (let i = 0; i < arrCommands.length; i++) {
1244
+ let cmd = arrCommands[i];
1245
+ if (typeof cmd.types !== 'undefined' && cmd.types.length > 0 && cmd.types.includes(cf.theme)) cmds.push(cmd);
1246
+ }
1247
+ return cmds;
1248
+ }
1249
+ else return [];
1250
+ }
1251
+
1189
1252
  public static getIdName(id: number) {
1190
1253
  // todo: adjust for intellitouch
1191
1254
  let defName = "Aux" + (id + 1).toString();
@@ -1197,6 +1260,9 @@ export class Circuit extends EqItem implements ICircuit {
1197
1260
  }
1198
1261
  export class FeatureCollection extends EqItemCollection<Feature> {
1199
1262
  constructor(data: any, name?: string) { super(data, name || "features"); }
1263
+ public filter(f: (value: Circuit, index?: any, array?: any[]) => boolean): FeatureCollection {
1264
+ return new FeatureCollection({ features: this.data.filter(f) });
1265
+ }
1200
1266
  public createItem(data: any): Feature { return new Feature(data); }
1201
1267
  }
1202
1268
  export class Feature extends EqItem implements ICircuit {
@@ -1206,6 +1272,7 @@ export class Feature extends EqItem implements ICircuit {
1206
1272
  if (typeof this.data.isActive === 'undefined') this.data.isActive = true;
1207
1273
  if (typeof this.data.eggTimer === 'undefined') this.data.eggTimer = 720;
1208
1274
  if (typeof this.data.showInFeatures === 'undefined') this.data.showInFeatures = true;
1275
+ if (typeof this.data.master === 'undefined') this.data.master = sys.board.equipmentMaster;
1209
1276
  }
1210
1277
  public dataName = 'featureConfig';
1211
1278
  public get id(): number { return this.data.id; }
@@ -1246,6 +1313,7 @@ export interface ICircuit {
1246
1313
  showInFeatures?: boolean;
1247
1314
  macro?: boolean;
1248
1315
  getLightThemes?: (type?: number) => {};
1316
+ getLightCommands?: (type?: number) => {};
1249
1317
  get(copy?: boolean);
1250
1318
  master: number;
1251
1319
  }
@@ -1266,9 +1334,12 @@ export class Pump extends EqItem {
1266
1334
  public dataName = 'pumpConfig';
1267
1335
  public initData() {
1268
1336
  if (typeof this.data.isVirtual !== 'undefined') delete this.data.isVirtual;
1337
+ if (typeof this.data.portId === 'undefined') this.data.portId = 0;
1269
1338
  }
1270
1339
  public get id(): number { return this.data.id; }
1271
1340
  public set id(val: number) { this.setDataVal('id', val); }
1341
+ public get portId(): number { return this.data.portId; }
1342
+ public set portId(val: number) { this.setDataVal('portId', val); }
1272
1343
  public get address(): number { return this.data.address || this.data.id + 95; }
1273
1344
  public set address(val: number) { this.setDataVal('address', val); }
1274
1345
  public get name(): string { return this.data.name; }
@@ -1412,6 +1483,13 @@ export class ChlorinatorCollection extends EqItemCollection<Chlorinator> {
1412
1483
  body === 32 && elem.body <= 2 ||
1413
1484
  elem.body === 32 && body <= 2);
1414
1485
  }
1486
+ public getItemByPortId(portId: number, add?: boolean, data?: any): Chlorinator {
1487
+ let itm = this.find(elem => { return typeof (elem as { portId?}).portId !== 'undefined' && (elem as { portId?}).portId === portId });
1488
+ if (typeof itm !== 'undefined') return itm;
1489
+ if (typeof add !== 'undefined' && add) return this.add(extend(true, { portId: portId }, data));
1490
+ return this.createItem(extend(true, data, { portId: portId }));
1491
+ }
1492
+ public findItemByPortId(portId: number) { return this.find(elem => { return typeof (elem as { portId?}).portId !== 'undefined' && (elem as { portId?}).portId === portId }); }
1415
1493
  }
1416
1494
  export class Chlorinator extends EqItem {
1417
1495
  public dataName = 'chlorinatorConfig';
@@ -1419,9 +1497,12 @@ export class Chlorinator extends EqItem {
1419
1497
  if (typeof this.data.disabled === 'undefined') this.data.disabled = false;
1420
1498
  if (typeof this.data.ignoreSaltReading === 'undefined') this.data.ignoreSaltReading = false;
1421
1499
  if (typeof this.data.isVirtual !== 'undefined') delete this.data.isVirtual;
1500
+ if (typeof this.data.portId === 'undefined') this.data.portId = 0;
1422
1501
  }
1423
1502
  public get id(): number { return this.data.id; }
1424
1503
  public set id(val: number) { this.setDataVal('id', val); }
1504
+ public get portId(): number { return this.data.portId; }
1505
+ public set portId(val: number) { this.setDataVal('portId', val); }
1425
1506
  public get type(): number | any { return this.data.type; }
1426
1507
  public set type(val: number | any) { this.setDataVal('type', sys.board.valueMaps.chlorinatorType.encode(val)); }
1427
1508
  public get body(): number | any { return this.data.body; }
@@ -1454,6 +1535,22 @@ export class Chlorinator extends EqItem {
1454
1535
  export class ValveCollection extends EqItemCollection<Valve> {
1455
1536
  constructor(data: any, name?: string) { super(data, name || "valves"); }
1456
1537
  public createItem(data: any): Valve { return new Valve(data); }
1538
+ public getIntake(): Valve[] {
1539
+ let valves = this.data.filter(x => x.isIntake === true);
1540
+ let ret = [];
1541
+ for (let i = 0; i < valves.length; i++) {
1542
+ ret.push(this.getItemById(valves[i].id));
1543
+ }
1544
+ return ret;
1545
+ }
1546
+ public getReturn(): Valve[] {
1547
+ let valves = this.data.filter(x => x.isReturn === true);
1548
+ let ret = [];
1549
+ for (let i = 0; i < valves.length; i++) {
1550
+ ret.push(this.getItemById(valves[i].id));
1551
+ }
1552
+ return ret;
1553
+ }
1457
1554
  }
1458
1555
  export class Valve extends EqItem {
1459
1556
  public dataName = 'valveConfig';
@@ -1492,14 +1589,33 @@ export class HeaterCollection extends EqItemCollection<Heater> {
1492
1589
  if (typeof add !== 'undefined' && add) return this.add(data || { id: this.data.length + 1, address: address });
1493
1590
  return this.createItem(data || { id: this.data.length + 1, address: address });
1494
1591
  }
1592
+ public filter(f: (value: Heater, index?: any, array?: any[]) => boolean): HeaterCollection {
1593
+ return new HeaterCollection({ heaters: this.data.filter(f) });
1594
+ }
1595
+
1596
+ public getSolarHeaters(bodyId?: number): EqItemCollection<Heater> {
1597
+ let htype = sys.board.valueMaps.heaterTypes.getValue('solar');
1598
+ return new HeaterCollection(this.data.filter(x => {
1599
+ if (x.type === htype) {
1600
+ if (typeof bodyId !== 'undefined') {
1601
+ if (!x.isActive) return false;
1602
+ return (bodyId === x.body || (sys.equipment.shared && x.body === 32)) ? true : false;
1603
+ }
1604
+ }
1605
+ return false;
1606
+ }));
1607
+ }
1495
1608
  }
1496
1609
  export class Heater extends EqItem {
1497
1610
  public dataName = 'heaterConfig';
1498
1611
  public initData() {
1499
1612
  if (typeof this.data.isActive === 'undefined') this.data.isActive = true;
1613
+ if (typeof this.data.portId === 'undefined') this.data.portId = 0;
1500
1614
  }
1501
1615
  public get id(): number { return this.data.id; }
1502
1616
  public set id(val: number) { this.setDataVal('id', val); }
1617
+ public get portId(): number { return this.data.portId; }
1618
+ public set portId(val: number) { this.setDataVal('portId', val); }
1503
1619
  public get type(): number | any { return this.data.type; }
1504
1620
  public set type(val: number | any) { this.setDataVal('type', sys.board.valueMaps.heaterTypes.encode(val)); }
1505
1621
  public get name(): string { return this.data.name; }
@@ -1532,6 +1648,8 @@ export class Heater extends EqItem {
1532
1648
  public set economyTime(val: number) { this.setDataVal('economyTime', val); }
1533
1649
  public get connectionId(): string { return this.data.connectionId; }
1534
1650
  public set connectionId(val: string) { this.setDataVal('connectionId', val); }
1651
+ public get minCycleTime(): number { return this.data.minCycleTime; }
1652
+ public set minCycleTime(val: number) { this.setDataVal('minCycleTime', val); }
1535
1653
  public get deviceBinding(): string { return this.data.deviceBinding; }
1536
1654
  public set deviceBinding(val: string) { this.setDataVal('deviceBinding', val); }
1537
1655
  }
@@ -1664,7 +1782,61 @@ export class LightGroup extends EqItem implements ICircuitGroup, ICircuit {
1664
1782
  public get lightingTheme(): number | any { return this.data.lightingTheme; }
1665
1783
  public set lightingTheme(val: number | any) { this.setDataVal('lightingTheme', sys.board.valueMaps.lightThemes.encode(val)); }
1666
1784
  public get circuits(): LightGroupCircuitCollection { return new LightGroupCircuitCollection(this.data, "circuits"); }
1667
- public getLightThemes() { return sys.board.valueMaps.lightThemes.toArray(); }
1785
+ public getLightThemes() {
1786
+ // Go through the circuits and gather the themes.
1787
+ // This method first looks at the circuits to determine their type (function)
1788
+ // then it filters the list by the types associated with the circuits. It does this because
1789
+ // there can be combined ColorLogic and IntelliBrite lights. The themes array has
1790
+ // the circuit function.
1791
+ let arrThemes = [];
1792
+ for (let i = 0; i < this.circuits.length; i++) {
1793
+ let circ = this.circuits.getItemByIndex(i);
1794
+ let c = sys.circuits.getInterfaceById(circ.circuit);
1795
+ let cf = sys.board.valueMaps.circuitFunctions.transform(c.type);
1796
+ if (cf.isLight && typeof cf.theme !== 'undefined') {
1797
+ if (!arrThemes.includes(cf.theme)) arrThemes.push(cf.theme);
1798
+ }
1799
+ }
1800
+ // Alright now we need to get a listing of the themes.
1801
+ let t = sys.board.valueMaps.lightThemes.toArray();
1802
+ let ret = [];
1803
+ for (let i = 0; i < t.length; i++) {
1804
+ let thm = t[i];
1805
+ if (typeof thm.types !== 'undefined' && thm.types.length > 0) {
1806
+ // Look in the themes array of the theme.
1807
+ if (arrThemes.some(x => thm.types.includes(x))) ret.push(thm);
1808
+ }
1809
+ }
1810
+ return ret;
1811
+ }
1812
+ public getLightCommands() {
1813
+ // Go through the circuits and gather the themes.
1814
+ // This method first looks at the circuits to determine their type (function)
1815
+ // then it filters the list by the types associated with the circuits. It does this because
1816
+ // there can be combined ColorLogic and IntelliBrite lights. The themes array has
1817
+ // the circuit function.
1818
+ let arrThemes = [];
1819
+ for (let i = 0; i < this.circuits.length; i++) {
1820
+ let circ = this.circuits.getItemByIndex(i);
1821
+ let c = sys.circuits.getInterfaceById(circ.circuit);
1822
+ let cf = sys.board.valueMaps.circuitFunctions.transform(c.type);
1823
+ if (cf.isLight && typeof cf.theme !== 'undefined') {
1824
+ if (!arrThemes.includes(cf.theme)) arrThemes.push(cf.theme);
1825
+ }
1826
+ }
1827
+ // Alright now we need to get a listing of the themes.
1828
+ let t = sys.board.valueMaps.lightGroupCommands.toArray();
1829
+ let ret = [];
1830
+ for (let i = 0; i < t.length; i++) {
1831
+ let cmd = t[i];
1832
+ if (typeof cmd.types !== 'undefined' && cmd.types.length > 0) {
1833
+ // Look in the themes array of the theme.
1834
+ if (arrThemes.some(x => cmd.types.includes(x))) ret.push(cmd);
1835
+ }
1836
+ }
1837
+ return ret;
1838
+ }
1839
+
1668
1840
  public getExtended() {
1669
1841
  let group = this.get(true);
1670
1842
  group.type = sys.board.valueMaps.circuitGroupTypes.transform(group.type);
@@ -1993,7 +2165,7 @@ export class ChemController extends EqItem {
1993
2165
  public getExtended() {
1994
2166
  let chem = this.get(true);
1995
2167
  chem.type = sys.board.valueMaps.chemControllerTypes.transform(this.type);
1996
- chem.siCalcType = sys.board.valueMaps.siCalcTypes.transform(this.type);
2168
+ chem.siCalcType = sys.board.valueMaps.siCalcTypes.transform(this.siCalcType || 0);
1997
2169
  chem.body = sys.board.valueMaps.bodies.transform(this.body);
1998
2170
  chem.ph = this.ph.getExtended();
1999
2171
  chem.orp = this.orp.getExtended();
@@ -2038,11 +2210,14 @@ export class Chemical extends ChildEqItem {
2038
2210
  if (typeof this.data.flowReadingsOnly === 'undefined') this.data.flowReadingsOnly = true;
2039
2211
  if (typeof this.data.flowOnlyMixing === 'undefined') this.data.flowOnlyMixing = true;
2040
2212
  if (typeof this.data.maxDailyVolume === 'undefined') this.data.maxDailyVolume = 500;
2213
+ if (typeof this.data.disableOnFreeze === 'undefined') this.data.disableOnFreeze = true;
2041
2214
  super.initData();
2042
2215
  }
2043
2216
  public get chemType(): string { return this.data.chemType; }
2044
2217
  public get enabled(): boolean { return utils.makeBool(this.data.enabled); }
2045
2218
  public set enabled(val: boolean) { this.setDataVal('enabled', val); }
2219
+ public get disableOnFreeze(): boolean { return utils.makeBool(this.data.disableOnFreeze); }
2220
+ public set disableOnFreeze(val: boolean) { this.setDataVal('disableOnFreeze', val); }
2046
2221
  public get maxDosingTime(): number { return this.data.maxDosingTime; }
2047
2222
  public set maxDosingTime(val: number) { this.setDataVal('maxDosingTime', val); }
2048
2223
  public get maxDosingVolume(): number { return this.data.maxDosingVolume; }
@@ -2083,6 +2258,7 @@ export class ChemicalPh extends Chemical {
2083
2258
  if (typeof this.data.acidType === 'undefined') this.data.acidType = 0;
2084
2259
  if (typeof this.data.tolerance === 'undefined') this.data.tolerance = { low: 7.2, high: 7.6, enabled: true };
2085
2260
  if (typeof this.data.dosePriority === 'undefined') this.data.dosePriority = true;
2261
+ if (typeof this.data.doserType === 'undefined') this.data.doserType = 0;
2086
2262
  super.initData();
2087
2263
  }
2088
2264
  public get phSupply(): number | any { return this.data.phSupply; }
@@ -2091,11 +2267,14 @@ export class ChemicalPh extends Chemical {
2091
2267
  public set acidType(val: number | any) { this.setDataVal('acidType', sys.board.valueMaps.acidTypes.encode(val)); }
2092
2268
  public get dosePriority(): boolean { return this.data.dosePriority; }
2093
2269
  public set dosePriority(val: boolean) { this.setDataVal('dosePriority', val); }
2270
+ public get doserType(): number | any { return this.data.doserType; }
2271
+ public set doserType(val: number | any) { this.setDataVal('doserType', sys.board.valueMaps.phDoserTypes.encode(val)); }
2094
2272
  public get probe(): ChemicalPhProbe { return new ChemicalPhProbe(this.data, 'probe', this); }
2095
2273
  public getExtended() {
2096
2274
  let chem = super.getExtended();
2097
2275
  chem.probe = this.probe.getExtended();
2098
2276
  chem.phSupply = sys.board.valueMaps.phSupplyTypes.transform(this.phSupply);
2277
+ chem.doserType = sys.board.valueMaps.phDoserTypes.transform(this.doserType);
2099
2278
  return chem;
2100
2279
  }
2101
2280
  }
@@ -2108,6 +2287,7 @@ export class ChemicalORP extends Chemical {
2108
2287
  if (typeof this.data.probe === 'undefined') this.data.probe = {};
2109
2288
  if (typeof this.data.tolerance === 'undefined') this.data.tolerance = { low: 650, high: 800, enabled: true };
2110
2289
  if (typeof this.data.phLockout === 'undefined') this.data.phLockout = 7.8;
2290
+ if (typeof this.data.doserType === 'undefined') this.data.doserType = 0;
2111
2291
  super.initData();
2112
2292
  }
2113
2293
  public get useChlorinator(): boolean { return utils.makeBool(this.data.useChlorinator); }
@@ -2117,9 +2297,13 @@ export class ChemicalORP extends Chemical {
2117
2297
  public get probe(): ChemicalORPProbe { return new ChemicalORPProbe(this.data, 'probe', this); }
2118
2298
  public get chlorDosingMethod(): number | any { return this.data.chlorDosingMethod; }
2119
2299
  public set chlorDosingMethod(val: number | any) { this.setDataVal('chlorDosingMethod', sys.board.valueMaps.chemChlorDosingMethods.encode(val)); }
2300
+ public get doserType(): number | any { return this.data.doserType; }
2301
+ public set doserType(val: number | any) { this.setDataVal('doserType', sys.board.valueMaps.orpDoserTypes.encode(val)); }
2302
+
2120
2303
  public getExtended() {
2121
2304
  let chem = super.getExtended();
2122
2305
  chem.probe = this.probe.getExtended();
2306
+ chem.doserType = sys.board.valueMaps.orpDoserTypes.transform(this.doserType);
2123
2307
  return chem;
2124
2308
  }
2125
2309
  }
@@ -144,6 +144,16 @@ export class ParameterOutOfRangeError extends InvalidOperationError {
144
144
  public value;
145
145
  public parameter: string;
146
146
  }
147
+ export class BoardProcessError extends ApiError {
148
+ constructor(message: string, process?: string) {
149
+ super(message, 300, 400);
150
+ this.name = 'ProcessingError';
151
+ this.process = process;
152
+ }
153
+ public process: string;
154
+
155
+ }
156
+
147
157
  export class MessageError extends ApiError {
148
158
  constructor(msg: Message, message: string, code?: number, httpCode?: number) {
149
159
  super(message, code, httpCode);