nodejs-poolcontroller 7.3.0 → 7.6.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 (60) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/Changelog +23 -0
  3. package/README.md +5 -5
  4. package/app.ts +2 -0
  5. package/config/Config.ts +3 -0
  6. package/config/VersionCheck.ts +8 -4
  7. package/controller/Constants.ts +88 -0
  8. package/controller/Equipment.ts +246 -66
  9. package/controller/Errors.ts +24 -1
  10. package/controller/Lockouts.ts +423 -0
  11. package/controller/State.ts +314 -54
  12. package/controller/boards/EasyTouchBoard.ts +107 -59
  13. package/controller/boards/IntelliCenterBoard.ts +186 -125
  14. package/controller/boards/IntelliTouchBoard.ts +104 -30
  15. package/controller/boards/NixieBoard.ts +721 -159
  16. package/controller/boards/SystemBoard.ts +2370 -1108
  17. package/controller/comms/Comms.ts +85 -10
  18. package/controller/comms/messages/Messages.ts +10 -4
  19. package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
  20. package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
  21. package/controller/comms/messages/config/CoverMessage.ts +1 -0
  22. package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
  23. package/controller/comms/messages/config/ExternalMessage.ts +44 -26
  24. package/controller/comms/messages/config/FeatureMessage.ts +8 -1
  25. package/controller/comms/messages/config/GeneralMessage.ts +8 -0
  26. package/controller/comms/messages/config/HeaterMessage.ts +15 -9
  27. package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
  28. package/controller/comms/messages/config/OptionsMessage.ts +13 -1
  29. package/controller/comms/messages/config/PumpMessage.ts +4 -20
  30. package/controller/comms/messages/config/RemoteMessage.ts +4 -0
  31. package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
  32. package/controller/comms/messages/config/SecurityMessage.ts +1 -0
  33. package/controller/comms/messages/config/ValveMessage.ts +13 -3
  34. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
  35. package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
  36. package/controller/comms/messages/status/HeaterStateMessage.ts +42 -9
  37. package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
  38. package/controller/nixie/Nixie.ts +18 -16
  39. package/controller/nixie/bodies/Body.ts +4 -1
  40. package/controller/nixie/chemistry/ChemController.ts +80 -77
  41. package/controller/nixie/chemistry/Chlorinator.ts +9 -8
  42. package/controller/nixie/circuits/Circuit.ts +55 -6
  43. package/controller/nixie/heaters/Heater.ts +192 -32
  44. package/controller/nixie/pumps/Pump.ts +146 -84
  45. package/controller/nixie/schedules/Schedule.ts +3 -2
  46. package/controller/nixie/valves/Valve.ts +1 -1
  47. package/defaultConfig.json +32 -1
  48. package/issue_template.md +1 -1
  49. package/logger/DataLogger.ts +37 -22
  50. package/package.json +20 -18
  51. package/web/Server.ts +520 -29
  52. package/web/bindings/influxDB.json +96 -8
  53. package/web/bindings/mqtt.json +151 -40
  54. package/web/bindings/mqttAlt.json +114 -4
  55. package/web/interfaces/httpInterface.ts +2 -0
  56. package/web/interfaces/influxInterface.ts +36 -19
  57. package/web/interfaces/mqttInterface.ts +14 -3
  58. package/web/services/config/Config.ts +171 -44
  59. package/web/services/state/State.ts +49 -5
  60. package/web/services/state/StateSocket.ts +18 -1
@@ -31,6 +31,7 @@ import { conn } from './comms/Comms';
31
31
  import { versionCheck } from "../config/VersionCheck";
32
32
  import { NixieControlPanel } from "./nixie/Nixie";
33
33
  import { NixieBoard } from 'controller/boards/NixieBoard';
34
+ import { child } from "winston";
34
35
 
35
36
  interface IPoolSystem {
36
37
  cfgPath: string;
@@ -68,7 +69,7 @@ interface IPoolSystem {
68
69
  }
69
70
 
70
71
  export class PoolSystem implements IPoolSystem {
71
- public _hasChanged: boolean=false;
72
+ public _hasChanged: boolean = false;
72
73
  constructor() {
73
74
  this.cfgPath = path.posix.join(process.cwd(), '/data/poolConfig.json');
74
75
  }
@@ -99,7 +100,12 @@ export class PoolSystem implements IPoolSystem {
99
100
  { val: 3, name: 'IT5S', part: 'i5+3S', desc: 'IntelliTouch i5+3S', bodies: 1, circuits: 6, shared: false },
100
101
  { val: 4, name: 'IT9S', part: 'i9+3S', desc: 'IntelliTouch i9+3S', bodies: 1, circuits: 9, shared: false },
101
102
  { val: 5, name: 'IT10D', part: 'i10D', desc: 'IntelliTouch i10D', bodies: 1, circuits: 10, shared: false, dual: true }
103
+ ],
104
+ expansionModules: [
105
+ { val: 32, name: 'IT5X', part: 'i5X', desc: 'IntelliTouch i5X', circuits: 5, shared: false },
106
+ { val: 33, name: 'IT10X', part: 'i10X', desc: 'IntelliTouch i10X', circuits: 10, shared: false }
102
107
  ]
108
+
103
109
  });
104
110
  arr.push({
105
111
  type: 'intellicenter', name: 'IntelliCenter',
@@ -109,7 +115,8 @@ export class PoolSystem implements IPoolSystem {
109
115
  { val: 2, name: 'i8P', part: '521977Z', desc: 'IntelliCenter i8P', bodies: 1, valves: 2, circuits: 8, shared: false, dual: false, chlorinators: 1, chemControllers: 1 },
110
116
  { val: 3, name: 'i8PS', part: '521968Z', desc: 'IntelliCenter i8PS', bodies: 2, valves: 4, circuits: 9, shared: true, dual: false, chlorinators: 1, chemControllers: 1 },
111
117
  { val: 4, name: 'i10P', part: '521993Z', desc: 'IntelliCenter i10P', bodies: 1, valves: 2, circuits: 10, shared: false, dual: false, chlorinators: 1, chemControllers: 1 }, // This is a guess
112
- { val: 5, name: 'i10PS', part: '521873Z', desc: 'IntelliCenter i10PS', bodies: 2, valves: 4, circuits: 11, shared: true, dual: false, chlorinators: 1, chemControllers: 1 }
118
+ { val: 5, name: 'i10PS', part: '521873Z', desc: 'IntelliCenter i10PS', bodies: 2, valves: 4, circuits: 11, shared: true, dual: false, chlorinators: 1, chemControllers: 1 },
119
+ { val: 7, name: 'i10D', part: '523029Z', desc: 'IntelliCenter i10D', bodies: 2, valves: 2, circuits: 11, shared: false, dual: true, chlorinators: 2, chemControllers: 2 },
113
120
  ]
114
121
  });
115
122
  arr.push({
@@ -140,7 +147,22 @@ export class PoolSystem implements IPoolSystem {
140
147
  let cfg = this.loadConfigFile(this.cfgPath, {});
141
148
  let cfgDefault = this.loadConfigFile(path.posix.join(process.cwd(), '/defaultPool.json'), {});
142
149
  cfg = extend(true, {}, cfgDefault, cfg);
143
- this.data = this.onchange(cfg, function() { sys.dirty = true; });
150
+ // First lets remove all the null ids.
151
+ EqItemCollection.removeNullIds(cfg.bodies);
152
+ EqItemCollection.removeNullIds(cfg.schedules);
153
+ EqItemCollection.removeNullIds(cfg.features);
154
+ EqItemCollection.removeNullIds(cfg.circuits);
155
+ EqItemCollection.removeNullIds(cfg.pumps);
156
+ EqItemCollection.removeNullIds(cfg.chlorinators);
157
+ EqItemCollection.removeNullIds(cfg.valves);
158
+ EqItemCollection.removeNullIds(cfg.heaters);
159
+ EqItemCollection.removeNullIds(cfg.covers);
160
+ EqItemCollection.removeNullIds(cfg.circuitGroups);
161
+ EqItemCollection.removeNullIds(cfg.lightGroups);
162
+ EqItemCollection.removeNullIds(cfg.remotes);
163
+ EqItemCollection.removeNullIds(cfg.chemControllers);
164
+ EqItemCollection.removeNullIds(cfg.filters);
165
+ this.data = this.onchange(cfg, function () { sys.dirty = true; });
144
166
  this.general = new General(this.data, 'pool');
145
167
  this.equipment = new Equipment(this.data, 'equipment');
146
168
  this.configVersion = new ConfigVersion(this.data, 'configVersion');
@@ -250,43 +272,6 @@ export class PoolSystem implements IPoolSystem {
250
272
  break;
251
273
  }
252
274
  }
253
- /* public searchForAdditionalDevices() {
254
- if (this.controllerType === ControllerType.Unknown || typeof this.controllerType === 'undefined' && !conn.mockPort) {
255
- //logger.info("Searching chlorinators, pumps and chem controllers");
256
- //EquipmentStateMessage.initVirtual();
257
- //sys.board.virtualChlorinatorController.search();
258
- //sys.board.virtualPumpControllers.search();
259
- //sys.board.virtualChemControllers.search();
260
- state.equipment.controllerType = sys.controllerType = ControllerType.Nixie;
261
- let board = sys.board as NixieBoard;
262
- (async () => { await board.initNixieBoard(); })();
263
- }
264
- else {
265
- if (this.controllerType === ControllerType.Virtual) {
266
- state.mode = 0;
267
- state.status = 1;
268
- sys.equipment.setEquipmentIds();
269
- state.emitControllerChange();
270
- sys.board.processStatusAsync();
271
- }
272
- else {
273
- let board = sys.board as NixieBoard;
274
- (async () => { await board.initNixieBoard(); })();
275
- }
276
-
277
- // if the app crashes while the pumps are running we need to reset the 'virtualControllerStatus' to stopped so it can start again
278
- sys.board.virtualPumpControllers.softStop();
279
- //sys.board.virtualChlorinatorController.stop();
280
- sys.board.virtualChemControllers.stop();
281
- // try to start any virtual controllers that are present irregardless of overall controller virtual status
282
- sys.board.virtualPumpControllers.start();
283
- //sys.board.virtualChlorinatorController.start();
284
- sys.board.virtualChemControllers.start();
285
- sys.board.heaters.initTempSensors();
286
- sys.board.heaters.updateHeaterServices();
287
- state.cleanupState();
288
- }
289
- } */
290
275
  public board: SystemBoard = new SystemBoard(this);
291
276
  public ncp: NixieControlPanel = new NixieControlPanel();
292
277
  public processVersionChanges(ver: ConfigVersion) { this.board.requestConfiguration(ver); }
@@ -537,6 +522,14 @@ class EqItemCollection<T> implements IEqItemCollection {
537
522
  this.name = name;
538
523
  }
539
524
  }
525
+ public static removeNullIds(data: any) {
526
+ if (typeof data !== 'undefined' && Array.isArray(data) && typeof data.length === 'number') {
527
+ for (let i = data.length - 1; i >= 0; i--) {
528
+ if (typeof data[i].id !== 'number') data.splice(i, 1);
529
+ else if (typeof data[i].id === 'undefined' || isNaN(data[i].id)) data.splice(i, 1);
530
+ }
531
+ }
532
+ }
540
533
  public getItemByIndex(ndx: number, add?: boolean, data?: any): T {
541
534
  if (this.data.length > ndx) return this.createItem(this.data[ndx]);
542
535
  if (typeof add !== 'undefined' && add)
@@ -620,7 +613,7 @@ class EqItemCollection<T> implements IEqItemCollection {
620
613
  }
621
614
  public sort(fn: (a, b) => number) { this.data.sort(fn); }
622
615
  public count(fn: () => boolean): number { return this.data.filter(fn).length; }
623
- public getNextEquipmentId(range: EquipmentIdRange, exclude?:number[]): number {
616
+ public getNextEquipmentId(range: EquipmentIdRange, exclude?: number[]): number {
624
617
  for (let i = range.start; i <= range.end; i++) {
625
618
  let eq = this.data.find(elem => elem.id === i);
626
619
  if (typeof eq === 'undefined') {
@@ -661,6 +654,7 @@ export class General extends EqItem {
661
654
  if (master === -1)
662
655
  super.clear();
663
656
  }
657
+
664
658
  }
665
659
  // Custom Names are IntelliTouch Only
666
660
  export class CustomNameCollection extends EqItemCollection<CustomName> {
@@ -729,25 +723,53 @@ export class Options extends EqItem {
729
723
  if (typeof this.data.units === 'undefined') this.data.units = 0;
730
724
  if (typeof this.data.clockMode === 'undefined') this.data.clockMode = 12;
731
725
  if (typeof this.data.adjustDST === 'undefined') this.data.adjustDST = true;
726
+ if (typeof this.data.freezeThreshold === 'undefined') this.data.freezeThreshold = 35;
727
+ if (typeof this.data.pumpDelay === 'undefined') this.data.pumpDelay = false;
728
+ if (typeof this.data.valveDelayTime === 'undefined') this.data.valveDelayTime = 30;
729
+ // RKS: 12-04-21 If you are reading this in a few months delete the line below.
730
+ if (this.data.valveDelayTime > 1000) this.data.valveDelayTime = this.data.valveDelayTime / 1000;
731
+ if (typeof this.data.heaterStartDelay === 'undefined') this.data.heaterStartDelay = true;
732
+ if (typeof this.data.cleanerStartDelay === 'undefined') this.data.cleanerStartDelay = true;
733
+ if (typeof this.data.cleanerSolarDelay === 'undefined') this.data.cleanerSolarDelay = true;
734
+ if (typeof this.data.heaterStartDelayTime === 'undefined') this.data.heaterStartDelayTime = 10;
735
+ if (typeof this.data.cleanerStartDelayTime === 'undefined') this.data.cleanerStartDelayTime = 300; // 5min
736
+ if (typeof this.data.cleanerSolarDelayTime === 'undefined') this.data.cleanerSolarDelayTime = 300; // 5min
732
737
  }
733
738
  public get clockMode(): number | any { return this.data.clockMode; }
734
739
  public set clockMode(val: number | any) { this.setDataVal('clockMode', sys.board.valueMaps.clockModes.encode(val)); }
735
740
  public get units(): number | any { return this.data.units; }
736
- public set units(val: number | any) { this.setDataVal('units', sys.board.valueMaps.tempUnits.encode(val)); }
741
+ public set units(val: number | any) { this.setDataVal('units', sys.board.valueMaps.systemUnits.encode(val)); }
737
742
  public get clockSource(): string { return this.data.clockSource; }
738
743
  public set clockSource(val: string) { this.setDataVal('clockSource', val); }
739
744
  public get adjustDST(): boolean { return this.data.adjustDST; }
740
745
  public set adjustDST(val: boolean) { this.setDataVal('adjustDST', val); }
741
746
  public get manualPriority(): boolean { return this.data.manualPriority; }
742
747
  public set manualPriority(val: boolean) { this.setDataVal('manualPriority', val); }
743
- public get vacationMode(): boolean { return this.data.vacationMode; }
744
- public set vacationMode(val: boolean) { this.setDataVal('vacationMode', val); }
748
+ public get vacation(): VacationOptions { return new VacationOptions(this.data, 'vacation', this); }
745
749
  public get manualHeat(): boolean { return this.data.manualHeat; }
746
750
  public set manualHeat(val: boolean) { this.setDataVal('manualHeat', val); }
747
751
  public get pumpDelay(): boolean { return this.data.pumpDelay; }
748
752
  public set pumpDelay(val: boolean) { this.setDataVal('pumpDelay', val); }
753
+ public get valveDelayTime(): number { return this.data.valveDelayTime; }
754
+ public set valveDelayTime(val: number) { this.setDataVal('valveDelayTime', val); }
749
755
  public get cooldownDelay(): boolean { return this.data.cooldownDelay; }
750
756
  public set cooldownDelay(val: boolean) { this.setDataVal('cooldownDelay', val); }
757
+ public get freezeThreshold(): number { return this.data.freezeThreshold; }
758
+ public set freezeThreshold(val: number) { this.setDataVal('freezeThreshold', val); }
759
+ public get heaterStartDelay(): boolean { return this.data.heaterStartDelay; }
760
+ public set heaterStartDelay(val: boolean) { this.setDataVal('heaterStartDelay', val); }
761
+ public get heaterStartDelayTime(): number { return this.data.heaterStartDelayTime; }
762
+ public set heaterStartDelayTime(val: number) { this.setDataVal('heaterStartDelayTime', val); }
763
+
764
+ public get cleanerStartDelay(): boolean { return this.data.cleanerStartDelay; }
765
+ public set cleanerStartDelay(val: boolean) { this.setDataVal('cleanerStartDelay', val); }
766
+ public get cleanerStartDelayTime(): number { return this.data.cleanerStartDelayTime; }
767
+ public set cleanerStartDelayTime(val: number) { this.setDataVal('cleanerStartDelayTime', val); }
768
+ public get cleanerSolarDelay(): boolean { return this.data.cleanerSolarDelay; }
769
+ public set cleanerSolarDelay(val: boolean) { this.setDataVal('cleanerSolarDelay', val); }
770
+ public get cleanerSolarDelayTime(): number { return this.data.cleanerSolarDelayTime; }
771
+ public set cleanerSolarDelayTime(val: number) { this.setDataVal('cleanerSolarDelayTime', val); }
772
+
751
773
  //public get airTempAdj(): number { return typeof this.data.airTempAdj === 'undefined' ? 0 : this.data.airTempAdj; }
752
774
  //public set airTempAdj(val: number) { this.setDataVal('airTempAdj', val); }
753
775
  //public get waterTempAdj1(): number { return typeof this.data.waterTempAdj1 === 'undefined' ? 0 : this.data.waterTempAdj1; }
@@ -759,6 +781,42 @@ export class Options extends EqItem {
759
781
  //public get solarTempAdj2(): number { return typeof this.data.solarTempAdj2 === 'undefined' ? 0 : this.data.solarTempAdj2; }
760
782
  //public set solarTempAdj2(val: number) { this.setDataVal('solarTempAd2', val); }
761
783
  }
784
+ export class VacationOptions extends ChildEqItem {
785
+ public initData() {
786
+ if (typeof this.data.enabled === 'undefined') this.data.enabled = false;
787
+ if (typeof this.data.useTimeframe === 'undefined') this.data.useTimeframe = false;
788
+ }
789
+ private _startDate: Date;
790
+ private _endDate: Date;
791
+ public get enabled(): boolean { return this.data.enabled; }
792
+ public set enabled(val: boolean) { this.setDataVal('enabled', val); }
793
+ public get useTimeframe(): boolean { return this.data.useTimeframe; }
794
+ public set useTimeframe(val: boolean) { this.setDataVal('useTimeframe', val); }
795
+ public get startDate() {
796
+ if (typeof this._startDate === 'undefined') this._startDate = typeof this.data.startDate === 'string' ? new Date(this.data.startDate) : undefined;
797
+ return this._startDate;
798
+ }
799
+ public set startDate(val: Date | string | number) {
800
+ this._startDate = new Date(val);
801
+ this._saveDate('startDate', this._startDate);
802
+ }
803
+ public get endDate() {
804
+ if (typeof this._endDate === 'undefined') this._endDate = typeof this.data.endDate === 'string' ? new Date(this.data.endDate) : undefined;
805
+ return this._endDate;
806
+ }
807
+ public set endDate(val: Date | string | number) {
808
+ this._endDate = new Date(val);
809
+ this._saveDate('endDate', this._endDate);
810
+ }
811
+
812
+ private _saveDate(prop: string, dt: Date) {
813
+ if (typeof dt !== 'undefined' && !isNaN(dt.getTime())) {
814
+ dt.setHours(0, 0, 0, 0);
815
+ this.setDataVal(prop, Timestamp.toISOLocal(dt));
816
+ }
817
+ else this.setDataVal(prop, undefined);
818
+ }
819
+ }
762
820
  export class Location extends EqItem {
763
821
  public dataName = 'locationConfig';
764
822
  public get address(): string { return this.data.address; }
@@ -823,6 +881,8 @@ export class Equipment extends EqItem {
823
881
  public set shared(val: boolean) { this.setDataVal('shared', val); }
824
882
  public get dual(): boolean { return this.data.dual; }
825
883
  public set dual(val: boolean) { this.setDataVal('dual', val); }
884
+ public get intakeReturnValves(): boolean { return this.data.intakeReturnValves; }
885
+ public set intakeReturnValves(val: boolean) { this.setDataVal('intakeReturnValves', val); }
826
886
  public get maxBodies(): number { return this.data.maxBodies || 4; }
827
887
  public set maxBodies(val: number) { this.setDataVal('maxBodies', val); }
828
888
  public get maxValves(): number { return this.data.maxValves || 26; }
@@ -944,7 +1004,6 @@ export class ConfigVersion extends EqItem {
944
1004
  if (prop === 'lastUpdated') continue;
945
1005
  this.data[prop] = 0;
946
1006
  }
947
-
948
1007
  }
949
1008
  }
950
1009
 
@@ -968,6 +1027,9 @@ export class BodyCollection extends EqItemCollection<Body> {
968
1027
  }
969
1028
  export class Body extends EqItem {
970
1029
  public dataName = 'bodyConfig';
1030
+ public initData() {
1031
+ if (typeof this.data.capacityUnits === 'undefined') this.data.capacityUnits = 1;
1032
+ }
971
1033
  public get id(): number { return this.data.id; }
972
1034
  public set id(val: number) { this.data.id = val; }
973
1035
  public get name(): string { return this.data.name; }
@@ -992,7 +1054,14 @@ export class Body extends EqItem {
992
1054
  public set heatSetpoint(val: number) { this.setDataVal('setPoint', val); }
993
1055
  public get coolSetpoint(): number { return this.data.coolSetpoint; }
994
1056
  public set coolSetpoint(val: number) { this.setDataVal('coolSetpoint', val); }
1057
+ public get capacityUnits(): number | any { return this.data.capacityUnits; }
1058
+ public set capacityUnits(val: number | any) { this.setDataVal('capacityUnits', sys.board.valueMaps.volumeUnits.encode(val)); }
995
1059
  public getHeatModes() { return sys.board.bodies.getHeatModes(this.id); }
1060
+ public getExtended() {
1061
+ let body = this.get(true);
1062
+ body.capacityUnits = sys.board.valueMaps.volumeUnits.transform(this.capacityUnits || 1);
1063
+ return body;
1064
+ }
996
1065
  }
997
1066
  export class ScheduleCollection extends EqItemCollection<Schedule> {
998
1067
  constructor(data: any, name?: string) { super(data, name || "schedules"); }
@@ -1040,12 +1109,12 @@ export class Schedule extends EqItem {
1040
1109
  public get isActive(): boolean { return this.data.isActive; }
1041
1110
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1042
1111
  public get startMonth(): number { return this._startDate.getMonth() + 1; }
1043
- public set startMonth(val: number) { this._startDate.setMonth(val - 1); this._saveStartDate(); }
1044
- public get startDay(): number { return this._startDate.getDate(); }
1045
- public set startDay(val: number) { this._startDate.setDate(val); this._saveStartDate(); }
1046
- public get startYear(): number { return this._startDate.getFullYear(); }
1047
- public set startYear(val: number) { this._startDate.setFullYear(val < 100 ? val + 2000 : val); this._saveStartDate(); }
1048
- public get startDate(): Date { return this._startDate; }
1112
+ public set startMonth(val: number) { if (typeof this._startDate === 'undefined') this._startDate = new Date(); this._startDate.setMonth(val - 1); this._saveStartDate(); }
1113
+ public get startDay(): number { if (typeof this._startDate === 'undefined') this._startDate = new Date(); return this._startDate.getDate(); }
1114
+ public set startDay(val: number) { if (typeof this._startDate === 'undefined') this._startDate = new Date(); this._startDate.setDate(val); this._saveStartDate(); }
1115
+ public get startYear(): number { if (typeof this._startDate === 'undefined') this._startDate = new Date(); return this._startDate.getFullYear(); }
1116
+ public set startYear(val: number) { if (typeof this._startDate === 'undefined') this._startDate = new Date(); this._startDate.setFullYear(val < 100 ? val + 2000 : val); this._saveStartDate(); }
1117
+ public get startDate(): Date { return typeof this._startDate === 'undefined' ? this._startDate = new Date() : this._startDate; }
1049
1118
  public set startDate(val: Date) { this._startDate = val; }
1050
1119
  public get scheduleType(): number | any { return this.data.scheduleType; }
1051
1120
  public set scheduleType(val: number | any) { this.setDataVal('scheduleType', sys.board.valueMaps.scheduleTypes.encode(val)); }
@@ -1085,6 +1154,9 @@ export class EggTimer extends EqItem {
1085
1154
  }
1086
1155
  export class CircuitCollection extends EqItemCollection<Circuit> {
1087
1156
  constructor(data: any, name?: string) { super(data, name || "circuits"); }
1157
+ public filter(f: (value: Circuit, index?: any, array?: any[]) => boolean): CircuitCollection {
1158
+ return new CircuitCollection({ circuits: this.data.filter(f) });
1159
+ }
1088
1160
  public createItem(data: any): Circuit { return new Circuit(data); }
1089
1161
  public add(obj: any): Circuit {
1090
1162
  this.data.push(obj);
@@ -1103,6 +1175,13 @@ export class CircuitCollection extends EqItemCollection<Circuit> {
1103
1175
  }
1104
1176
  export class Circuit extends EqItem implements ICircuit {
1105
1177
  public dataName = 'circuitConfig';
1178
+ public initData() {
1179
+ if (typeof this.data.freeze === 'undefined') this.data.freeze = false;
1180
+ if (typeof this.data.type === 'undefined') this.data.type = 0;
1181
+ if (typeof this.data.isActive === 'undefined') this.data.isActive = false;
1182
+ if (typeof this.data.eggTimer === 'undefined') this.data.eggTimer = 720;
1183
+ if (typeof this.data.showInFeatures === 'undefined') this.data.showInFeatures = true;
1184
+ }
1106
1185
  public get id(): number { return this.data.id; }
1107
1186
  public set id(val: number) { this.setDataVal('id', val); }
1108
1187
  public get name(): string { return this.data.name; }
@@ -1134,7 +1213,22 @@ export class Circuit extends EqItem implements ICircuit {
1134
1213
  public get deviceBinding(): string { return this.data.deviceBinding; }
1135
1214
  public set deviceBinding(val: string) { this.setDataVal('deviceBinding', val); }
1136
1215
  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 };
1137
- public getLightThemes() { return sys.board.circuits.getLightThemes(this.type); }
1216
+ public getLightThemes() {
1217
+ // Lets do this universally driven by the metadata.
1218
+ let cf = sys.board.valueMaps.circuitFunctions.transform(this.type);
1219
+ if (cf.isLight && typeof cf.theme !== 'undefined') {
1220
+ let arrThemes = sys.board.valueMaps.lightThemes.toArray();
1221
+ let themes = [];
1222
+ for (let i = 0; i < arrThemes.length; i++) {
1223
+ let thm = arrThemes[i];
1224
+ if (typeof thm.types !== 'undefined' && thm.types.length > 0 && thm.types.includes(cf.theme)) themes.push(thm);
1225
+ }
1226
+ return themes;
1227
+ }
1228
+ else return [];
1229
+
1230
+ //return sys.board.circuits.getLightThemes(this.type);
1231
+ }
1138
1232
  public static getIdName(id: number) {
1139
1233
  // todo: adjust for intellitouch
1140
1234
  let defName = "Aux" + (id + 1).toString();
@@ -1146,6 +1240,9 @@ export class Circuit extends EqItem implements ICircuit {
1146
1240
  }
1147
1241
  export class FeatureCollection extends EqItemCollection<Feature> {
1148
1242
  constructor(data: any, name?: string) { super(data, name || "features"); }
1243
+ public filter(f: (value: Circuit, index?: any, array?: any[]) => boolean): FeatureCollection {
1244
+ return new FeatureCollection({ features: this.data.filter(f) });
1245
+ }
1149
1246
  public createItem(data: any): Feature { return new Feature(data); }
1150
1247
  }
1151
1248
  export class Feature extends EqItem implements ICircuit {
@@ -1155,6 +1252,7 @@ export class Feature extends EqItem implements ICircuit {
1155
1252
  if (typeof this.data.isActive === 'undefined') this.data.isActive = true;
1156
1253
  if (typeof this.data.eggTimer === 'undefined') this.data.eggTimer = 720;
1157
1254
  if (typeof this.data.showInFeatures === 'undefined') this.data.showInFeatures = true;
1255
+ if (typeof this.data.master === 'undefined') this.data.master = sys.board.equipmentMaster;
1158
1256
  }
1159
1257
  public dataName = 'featureConfig';
1160
1258
  public get id(): number { return this.data.id; }
@@ -1194,7 +1292,7 @@ export interface ICircuit {
1194
1292
  //showInCircuits?: boolean;
1195
1293
  showInFeatures?: boolean;
1196
1294
  macro?: boolean;
1197
- getLightThemes?: () => {};
1295
+ getLightThemes?: (type?: number) => {};
1198
1296
  get(copy?: boolean);
1199
1297
  master: number;
1200
1298
  }
@@ -1213,6 +1311,9 @@ export class PumpCollection extends EqItemCollection<Pump> {
1213
1311
  }
1214
1312
  export class Pump extends EqItem {
1215
1313
  public dataName = 'pumpConfig';
1314
+ public initData() {
1315
+ if (typeof this.data.isVirtual !== 'undefined') delete this.data.isVirtual;
1316
+ }
1216
1317
  public get id(): number { return this.data.id; }
1217
1318
  public set id(val: number) { this.setDataVal('id', val); }
1218
1319
  public get address(): number { return this.data.address || this.data.id + 95; }
@@ -1259,8 +1360,10 @@ export class Pump extends EqItem {
1259
1360
  public set vacuumTime(val: number) { this.setDataVal('vacuumTime', val); }
1260
1361
  public get backgroundCircuit() { return this.data.backgroundCircuit; }
1261
1362
  public set backgroundCircuit(val: number) { this.setDataVal('backgroundCircuit', val); }
1262
- public get isVirtual() { return this.data.isVirtual; }
1263
- public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
1363
+ public get filterSize() { return this.data.filterSize; }
1364
+ public set filterSize(val: number) { this.setDataVal('filterSize', val); }
1365
+ // public get isVirtual() { return this.data.isVirtual; }
1366
+ // public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
1264
1367
  public get connectionId(): string { return this.data.connectionId; }
1265
1368
  public set connectionId(val: string) { this.setDataVal('connectionId', val); }
1266
1369
  public get deviceBinding(): string { return this.data.deviceBinding; }
@@ -1398,6 +1501,22 @@ export class Chlorinator extends EqItem {
1398
1501
  export class ValveCollection extends EqItemCollection<Valve> {
1399
1502
  constructor(data: any, name?: string) { super(data, name || "valves"); }
1400
1503
  public createItem(data: any): Valve { return new Valve(data); }
1504
+ public getIntake(): Valve[] {
1505
+ let valves = this.data.filter(x => x.isIntake === true);
1506
+ let ret = [];
1507
+ for (let i = 0; i < valves.length; i++) {
1508
+ ret.push(this.getItemById(valves[i].id));
1509
+ }
1510
+ return ret;
1511
+ }
1512
+ public getReturn(): Valve[] {
1513
+ let valves = this.data.filter(x => x.isReturn === true);
1514
+ let ret = [];
1515
+ for (let i = 0; i < valves.length; i++) {
1516
+ ret.push(this.getItemById(valves[i].id));
1517
+ }
1518
+ return ret;
1519
+ }
1401
1520
  }
1402
1521
  export class Valve extends EqItem {
1403
1522
  public dataName = 'valveConfig';
@@ -1416,8 +1535,8 @@ export class Valve extends EqItem {
1416
1535
  public set isIntake(val: boolean) { this.setDataVal('isIntake', val); }
1417
1536
  public get isReturn(): boolean { return utils.makeBool(this.data.isReturn); }
1418
1537
  public set isReturn(val: boolean) { this.setDataVal('isReturn', val); }
1419
- public get isVirtual(): boolean { return utils.makeBool(this.data.isVirtual); }
1420
- public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
1538
+ // public get isVirtual(): boolean { return utils.makeBool(this.data.isVirtual); }
1539
+ // public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
1421
1540
  public get pinId(): number { return this.data.pinId || 0; }
1422
1541
  public set pinId(val: number) { this.setDataVal('pinId', val); }
1423
1542
  public get isActive(): boolean { return this.data.isActive; }
@@ -1436,6 +1555,22 @@ export class HeaterCollection extends EqItemCollection<Heater> {
1436
1555
  if (typeof add !== 'undefined' && add) return this.add(data || { id: this.data.length + 1, address: address });
1437
1556
  return this.createItem(data || { id: this.data.length + 1, address: address });
1438
1557
  }
1558
+ public filter(f: (value: Heater, index?: any, array?: any[]) => boolean): HeaterCollection {
1559
+ return new HeaterCollection({ heaters: this.data.filter(f) });
1560
+ }
1561
+
1562
+ public getSolarHeaters(bodyId?: number): EqItemCollection<Heater> {
1563
+ let htype = sys.board.valueMaps.heaterTypes.getValue('solar');
1564
+ return new HeaterCollection(this.data.filter(x => {
1565
+ if (x.type === htype) {
1566
+ if (typeof bodyId !== 'undefined') {
1567
+ if (!x.isActive) return false;
1568
+ return (bodyId === x.body || (sys.equipment.shared && x.body === 32)) ? true : false;
1569
+ }
1570
+ }
1571
+ return false;
1572
+ }));
1573
+ }
1439
1574
  }
1440
1575
  export class Heater extends EqItem {
1441
1576
  public dataName = 'heaterConfig';
@@ -1464,8 +1599,6 @@ export class Heater extends EqItem {
1464
1599
  public set efficiencyMode(val: number) { this.setDataVal('efficiencyMode', val); }
1465
1600
  public get isActive(): boolean { return this.data.isActive; }
1466
1601
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1467
- public get isVirtual(): boolean { return this.data.isVirtual; }
1468
- public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
1469
1602
  public get coolingEnabled(): boolean { return this.data.coolingEnabled; }
1470
1603
  public set coolingEnabled(val: boolean) { this.setDataVal('coolingEnabled', val); }
1471
1604
  public get heatingEnabled(): boolean { return this.data.heatingEnabled; }
@@ -1502,6 +1635,10 @@ export class Cover extends EqItem {
1502
1635
  public set normallyOn(val: boolean) { this.setDataVal('normallyOn', val); }
1503
1636
  public get circuits(): number[] { return this.data.circuits; }
1504
1637
  public set circuits(val: number[]) { this.setDataVal('circuits', val); }
1638
+ public get chlorActive(): boolean { return this.data.chlorActive; }
1639
+ public set chlorActive(val: boolean) { this.setDataVal('chlorActive', val); }
1640
+ public get chlorOutput(): boolean { return this.data.chlorOutput; }
1641
+ public set chlorOutput(val: boolean) { this.setDataVal('chlorOutput', val); }
1505
1642
  }
1506
1643
  export interface ICircuitGroup {
1507
1644
  id: number;
@@ -1606,7 +1743,33 @@ export class LightGroup extends EqItem implements ICircuitGroup, ICircuit {
1606
1743
  public get lightingTheme(): number | any { return this.data.lightingTheme; }
1607
1744
  public set lightingTheme(val: number | any) { this.setDataVal('lightingTheme', sys.board.valueMaps.lightThemes.encode(val)); }
1608
1745
  public get circuits(): LightGroupCircuitCollection { return new LightGroupCircuitCollection(this.data, "circuits"); }
1609
- public getLightThemes() { return sys.board.valueMaps.lightThemes.toArray(); }
1746
+ public getLightThemes() {
1747
+ // Go through the circuits and gather the themes.
1748
+ // This method first looks at the circuits to determine their type (function)
1749
+ // then it filters the list by the types associated with the circuits. It does this because
1750
+ // there can be combined ColorLogic and IntelliBrite lights. The themes array has
1751
+ // the circuit function.
1752
+ let arrThemes = [];
1753
+ for (let i = 0; i < this.circuits.length; i++) {
1754
+ let circ = this.circuits.getItemByIndex(i);
1755
+ let c = sys.circuits.getInterfaceById(circ.circuit);
1756
+ let cf = sys.board.valueMaps.circuitFunctions.transform(c.type);
1757
+ if (cf.isLight && typeof cf.theme !== 'undefined') {
1758
+ if (!arrThemes.includes(cf.theme)) arrThemes.push(cf.theme);
1759
+ }
1760
+ }
1761
+ // Alright now we need to get a listing of the themes.
1762
+ let t = sys.board.valueMaps.lightThemes.toArray();
1763
+ let ret = [];
1764
+ for (let i = 0; i < t.length; i++) {
1765
+ let thm = t[i];
1766
+ if (typeof thm.types !== 'undefined' && thm.types.length > 0) {
1767
+ // Look in the themes array of the theme.
1768
+ if (arrThemes.some(x => thm.types.includes(x))) ret.push(thm);
1769
+ }
1770
+ }
1771
+ return ret;
1772
+ }
1610
1773
  public getExtended() {
1611
1774
  let group = this.get(true);
1612
1775
  group.type = sys.board.valueMaps.circuitGroupTypes.transform(group.type);
@@ -1823,7 +1986,7 @@ export class ChemController extends EqItem {
1823
1986
  //var chemController = {
1824
1987
  // id: 'number', // Id of the controller
1825
1988
  // name: 'string', // Name assigned to the controller
1826
- // type: 'valueMap', // intellichem, homegrown, rem -- There is an unknown but that should probably go away.
1989
+ // type: 'valueMap', // intellichem, rem -- There is an unknown but that should probably go away.
1827
1990
  // body: 'valueMap', // Body assigned to the chem controller.
1828
1991
  // address: 'number', // Address for IntelliChem controller only.
1829
1992
  // isActive: 'booean',
@@ -1912,8 +2075,8 @@ export class ChemController extends EqItem {
1912
2075
  public set address(val: number) { this.setDataVal('address', val); }
1913
2076
  public get isActive(): boolean { return this.data.isActive; }
1914
2077
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
1915
- public get isVirtual(): boolean { return this.data.isVirtual; }
1916
- public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
2078
+ // public get isVirtual(): boolean { return this.data.isVirtual; }
2079
+ // public set isVirtual(val: boolean) { this.setDataVal('isVirtual', val); }
1917
2080
  public get calciumHardness(): number { return this.data.calciumHardness; }
1918
2081
  public set calciumHardness(val: number) { this.setDataVal('calciumHardness', val); }
1919
2082
  public get cyanuricAcid(): number { return this.data.cyanuricAcid; }
@@ -2075,6 +2238,11 @@ export class Filter extends EqItem {
2075
2238
  if (typeof this.data.filterType === 'undefined') this.data.filterType = 3;
2076
2239
  if (typeof this.data.capacity === 'undefined') this.data.capacity = 0;
2077
2240
  if (typeof this.data.capacityUnits === 'undefined') this.data.capacityUnits = 0;
2241
+ if (typeof this.data.cleanPressure === 'undefined') this.data.cleanPressure = 0;
2242
+ if (typeof this.data.dirtyPressure === 'undefined') this.data.dirtyPressure = 0;
2243
+ if (typeof this.data.pressureUnits === 'undefined') this.data.pressureUnits = 0;
2244
+ // Start this out at pool and let the user switch it around if necessary.
2245
+ if (typeof this.data.pressureCircuitId === 'undefined') this.data.pressureCircuitId = 6;
2078
2246
  }
2079
2247
  public get id(): number { return this.data.id; }
2080
2248
  public set id(val: number) { this.setDataVal('id', val); }
@@ -2090,10 +2258,22 @@ export class Filter extends EqItem {
2090
2258
  public set isActive(val: boolean) { this.setDataVal('isActive', val); }
2091
2259
  public get name(): string { return this.data.name; }
2092
2260
  public set name(val: string) { this.setDataVal('name', val); }
2261
+
2262
+ public get showPressure(): boolean { return this.data.showPressure; }
2263
+ public set showPressure(val: boolean) { this.setDataVal('showPressure', val); }
2093
2264
  public get lastCleanDate(): Timestamp { return this.data.lastCleanDate; }
2094
2265
  public set lastCleanDate(val: Timestamp) { this.setDataVal('lastCleanDate', val); }
2095
2266
  public get needsCleaning(): number { return this.data.needsCleaning; }
2096
2267
  public set needsCleaning(val: number) { this.setDataVal('needsCleaning', val); }
2268
+ public get pressureUnits(): number { return this.data.pressureUnits; }
2269
+ public set pressureUnits(val: number) { this.setDataVal('pressureUnits', val); }
2270
+ public get pressureCircuitId(): number { return this.data.pressureCircuitId; }
2271
+ public set pressureCircuitId(val: number) { this.setDataVal('pressureCircuitId', val); }
2272
+ public get cleanPressure(): number { return this.data.cleanPressure; }
2273
+ public set cleanPressure(val: number) { this.setDataVal('cleanPressure', val); }
2274
+ public get dirtyPressure(): number { return this.data.dirtyPressure; }
2275
+ public set dirtyPressure(val: number) { this.setDataVal('dirtyPressure', val); }
2276
+
2097
2277
  public get connectionId(): string { return this.data.connectionId; }
2098
2278
  public set connectionId(val: string) { this.setDataVal('connectionId', val); }
2099
2279
  public get deviceBinding(): string { return this.data.deviceBinding; }
@@ -2183,7 +2363,7 @@ export class ChemicalChlor extends ChildEqItem {
2183
2363
  public initData() {
2184
2364
  super.initData();
2185
2365
  }
2186
- public get enabled(): boolean {
2366
+ public get enabled(): boolean {
2187
2367
  let chlor = sys.chlorinators.getItemById(1);
2188
2368
  return chlor.isActive;
2189
2369
  }
@@ -2200,10 +2380,10 @@ export class ChemicalChlor extends ChildEqItem {
2200
2380
  return typeof model.chlorinePerSec !== 'undefined' ? model.chlorinePerSec : 0;
2201
2381
  }
2202
2382
  public set ratedLbs(val: number) { this.setDataVal('ratedLbs', val); }
2203
- public get superChlor() {
2383
+ public get superChlor() {
2204
2384
  let chlor = sys.chlorinators.getItemById(1);
2205
2385
  return typeof chlor.superChlor !== 'undefined' ? chlor.superChlor : false;
2206
- }
2386
+ }
2207
2387
  public getExtended() {
2208
2388
  let chlor = this.get(true);
2209
2389
  chlor.model = sys.board.valueMaps.chlorinatorModel.transform(this.model);
@@ -96,15 +96,28 @@ export class InvalidEquipmentDataError extends EquipmentError {
96
96
  }
97
97
  public eqData;
98
98
  }
99
+ export class ServiceProcessError extends ApiError {
100
+ constructor(message: string, serviceName: string, process?: string) {
101
+ super(message, 290, 400);
102
+ this.name = 'ServiceProcessError';
103
+ this.service = serviceName;
104
+ this.process = process;
105
+ }
106
+ public process: string;
107
+ public service: string;
108
+ }
99
109
  export class ServiceParameterError extends ApiError {
100
110
  constructor(message: string, serviceName: string, paramName: string, value) {
101
111
  super(message, 280, 400);
102
112
  this.name = 'InvalidServiceParameter';
113
+
103
114
  this.value = value;
104
- this.parameter = value;
115
+ this.parameter = paramName;
116
+ this.service = serviceName;
105
117
  }
106
118
  public value;
107
119
  public parameter: string;
120
+ public service: string;
108
121
  }
109
122
  export class InvalidOperationError extends ApiError {
110
123
  constructor(message: string, operation: string) {
@@ -131,6 +144,16 @@ export class ParameterOutOfRangeError extends InvalidOperationError {
131
144
  public value;
132
145
  public parameter: string;
133
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
+
134
157
  export class MessageError extends ApiError {
135
158
  constructor(msg: Message, message: string, code?: number, httpCode?: number) {
136
159
  super(message, code, httpCode);