nodejs-poolcontroller 8.0.0 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.docker/Dockerfile.armv6 +29 -0
  2. package/.docker/Dockerfile.armv7 +29 -0
  3. package/.docker/Dockerfile.linux +62 -0
  4. package/.docker/Dockerfile.windows +43 -0
  5. package/.docker/docker-compose.yml +47 -0
  6. package/.docker/ecosystem.config.js +35 -0
  7. package/.github/workflows/docker-publish-njsPC-linux.yml +81 -0
  8. package/.github/workflows/docker-publish-njsPC-windows.yml +41 -0
  9. package/Dockerfile +4 -3
  10. package/README.md +4 -1
  11. package/config/Config.ts +1 -1
  12. package/controller/Constants.ts +164 -67
  13. package/controller/Equipment.ts +79 -18
  14. package/controller/Lockouts.ts +15 -0
  15. package/controller/State.ts +280 -7
  16. package/controller/boards/EasyTouchBoard.ts +226 -102
  17. package/controller/boards/IntelliCenterBoard.ts +67 -18
  18. package/controller/boards/IntelliTouchBoard.ts +2 -4
  19. package/controller/boards/NixieBoard.ts +84 -27
  20. package/controller/boards/SunTouchBoard.ts +8 -2
  21. package/controller/boards/SystemBoard.ts +3259 -3242
  22. package/controller/comms/ScreenLogic.ts +60 -57
  23. package/controller/comms/messages/Messages.ts +4 -4
  24. package/controller/comms/messages/config/ChlorinatorMessage.ts +10 -3
  25. package/controller/comms/messages/config/ExternalMessage.ts +4 -1
  26. package/controller/comms/messages/config/PumpMessage.ts +8 -7
  27. package/controller/comms/messages/config/RemoteMessage.ts +48 -43
  28. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -2
  29. package/controller/comms/messages/status/EquipmentStateMessage.ts +9 -4
  30. package/controller/nixie/NixieEquipment.ts +1 -1
  31. package/controller/nixie/bodies/Body.ts +1 -1
  32. package/controller/nixie/chemistry/ChemController.ts +37 -28
  33. package/controller/nixie/circuits/Circuit.ts +36 -0
  34. package/controller/nixie/heaters/Heater.ts +24 -5
  35. package/controller/nixie/pumps/Pump.ts +155 -97
  36. package/controller/nixie/schedules/Schedule.ts +207 -126
  37. package/defaultConfig.json +4 -3
  38. package/logger/DataLogger.ts +7 -7
  39. package/package.json +3 -3
  40. package/sendSocket.js +32 -0
  41. package/web/Server.ts +17 -11
  42. package/web/bindings/homeassistant.json +2 -2
  43. package/web/interfaces/mqttInterface.ts +18 -18
  44. package/web/services/config/Config.ts +34 -1
  45. package/web/services/state/State.ts +10 -3
  46. package/web/services/state/StateSocket.ts +7 -3
  47. package/web/services/utilities/Utilities.ts +3 -3
@@ -23,10 +23,11 @@ import * as util from 'util';
23
23
  import { logger } from '../logger/Logger';
24
24
  import { webApp } from '../web/Server';
25
25
  import { ControllerType, Timestamp, utils, Heliotrope } from './Constants';
26
- import { sys, Chemical, ChemController, ChemicalTank, ChemicalPump } from './Equipment';
26
+ import { sys, Chemical, ChemController, ChemicalTank, ChemicalPump, Schedule } from './Equipment';
27
27
  import { versionCheck } from '../config/VersionCheck';
28
28
  import { DataLogger, DataLoggerEntry } from '../logger/DataLogger';
29
29
  import { delayMgr } from './Lockouts';
30
+ import { time } from 'console';
30
31
 
31
32
  export class State implements IState {
32
33
  statePath: string;
@@ -212,6 +213,8 @@ export class State implements IState {
212
213
  model: sys.equipment.model,
213
214
  sunrise: self.data.sunrise || '',
214
215
  sunset: self.data.sunset || '',
216
+ nextSunrise: self.data.nextSunrise || '',
217
+ nextSunset: self.data.nextSunset || '',
215
218
  alias: sys.general.alias,
216
219
  freeze: utils.makeBool(self.data.freeze),
217
220
  valveMode: self.data.valveMode || {},
@@ -283,7 +286,10 @@ export class State implements IState {
283
286
  }
284
287
  }
285
288
  }
286
- public get time(): Timestamp { return this._dt; }
289
+ public get time(): Timestamp {
290
+ if (typeof this._dt === 'undefined' || !this._dt.isValid) this._dt = new Timestamp(new Date());
291
+ return this._dt;
292
+ }
287
293
  public get mode(): number { return typeof (this.data.mode) !== 'undefined' ? this.data.mode.val : -1; }
288
294
  public set mode(val: number) {
289
295
  let m = sys.board.valueMaps.panelModes.transform(val);
@@ -365,8 +371,16 @@ export class State implements IState {
365
371
  EqStateCollection.removeNullIds(sdata.chemControllers);
366
372
  EqStateCollection.removeNullIds(sdata.chemDosers);
367
373
  EqStateCollection.removeNullIds(sdata.filters);
374
+ // Initialize the schedules.
375
+ if (typeof sdata.schedules !== 'undefined') {
376
+ for (let i = 0; i < sdata.schedules.length; i++) {
377
+ let ssched = sdata.schedules[i];
378
+ ssched.manualPriorityActive = ssched.isOn = ssched.triggered = false;
379
+ if (typeof ssched.scheduleTime !== 'undefined') ssched.scheduleTime.calculated = false;
380
+ }
381
+ }
368
382
  var self = this;
369
- let pnlTime = typeof sdata.time !== 'undefined' ? new Date(sdata.time) : new Date();
383
+ let pnlTime = typeof sdata.time !== 'undefined' && sdata.time !== '' ? new Date(sdata.time) : new Date();
370
384
  if (isNaN(pnlTime.getTime())) pnlTime = new Date();
371
385
  this._dt = new Timestamp(pnlTime);
372
386
  this._dt.milliseconds = 0;
@@ -384,6 +398,10 @@ export class State implements IState {
384
398
  let times = self.heliotrope.calculatedTimes;
385
399
  self.data.sunrise = times.isValid ? Timestamp.toISOLocal(times.sunrise) : '';
386
400
  self.data.sunset = times.isValid ? Timestamp.toISOLocal(times.sunset) : '';
401
+ self.data.nextSunrise = times.isValid ? Timestamp.toISOLocal(times.nextSunrise) : '';
402
+ self.data.nextSunset = times.isValid ? Timestamp.toISOLocal(times.nextSunset) : '';
403
+ self.data.prevSunrise = times.isValid ? Timestamp.toISOLocal(times.prevSunrise) : '';
404
+ self.data.prevSunset = times.isValid ? Timestamp.toISOLocal(times.prevSunset) : '';
387
405
  versionCheck.checkGitRemote();
388
406
  });
389
407
  this.status = 0; // Initializing
@@ -507,6 +525,7 @@ export interface ICircuitState {
507
525
  isOn: boolean;
508
526
  startTime?: Timestamp;
509
527
  endTime: Timestamp;
528
+ priority?: string,
510
529
  lightingTheme?: number;
511
530
  action?: number;
512
531
  emitEquipmentChange();
@@ -681,7 +700,7 @@ class EqStateCollection<T> {
681
700
  return arr;
682
701
  }
683
702
  // Finds an item and returns undefined if it doesn't exist.
684
- public find(f: (value: any, index?: number, obj?: any) => boolean): T {
703
+ public find(f: (value: T, index?: number, obj?: any) => boolean): T {
685
704
  let itm = this.data.find(f);
686
705
  if (typeof itm !== 'undefined') return this.createItem(itm);
687
706
  }
@@ -1064,6 +1083,224 @@ export class PumpState extends EqState {
1064
1083
  }
1065
1084
  export class ScheduleStateCollection extends EqStateCollection<ScheduleState> {
1066
1085
  public createItem(data: any): ScheduleState { return new ScheduleState(data); }
1086
+ public getActiveSchedules(): ScheduleState[] {
1087
+ let activeScheds: ScheduleState[] = [];
1088
+ for (let i = 0; i < this.length; i++) {
1089
+ let ssched = this.getItemByIndex(i);
1090
+ let st = ssched.scheduleTime;
1091
+ let sched = sys.schedules.getItemById(ssched.id);
1092
+ if (!sched.isActive || ssched.disabled) {
1093
+ continue;
1094
+ }
1095
+ st.calcSchedule(state.time, sys.schedules.getItemById(ssched.id));
1096
+ if (typeof st.startTime === 'undefined') continue;
1097
+ if (ssched.isOn || st.shouldBeOn || st.startTime.getTime() > new Date().getTime()) activeScheds.push(ssched);
1098
+ }
1099
+ return activeScheds;
1100
+ }
1101
+ }
1102
+ export class ScheduleTime extends ChildEqState {
1103
+ public initData() { if (typeof this.data.times !== 'undefined') delete this.data.times; }
1104
+ public get calculatedDate(): Date { return typeof this.data.calculatedDate !== 'undefined' && this.data.calculatedDate !== '' ? new Date(this.data.calculatedDate) : new Date(1970, 0, 1); }
1105
+ public set calculatedDate(val: Date) { this._saveTimestamp(val, 'calculatedDate', false); }
1106
+ public get startTime(): Date { return typeof this.data.startTime !== 'undefined' && this.data.startTime !== '' ? new Date(this.data.startTime) : null; }
1107
+ public set startTime(val: Date) { this._saveTimestamp(val, 'startTime'); }
1108
+ public get endTime(): Date { return typeof this.data.endTime !== 'undefined' && this.data.endTime !== '' ? new Date(this.data.endTime) : null; }
1109
+ public set endTime(val: Date) { this._saveTimestamp(val, 'endTime'); }
1110
+ private _saveTimestamp(dt, prop, persist:boolean = true) {
1111
+ if (typeof dt === 'undefined' || !dt) this.setDataVal(prop, '');
1112
+ else this.setDataVal(prop, Timestamp.toISOLocal(dt));
1113
+ }
1114
+ private _calcShouldBeOn(time: number) : boolean {
1115
+ let tmStart = this.startTime ? this.startTime.getTime() : NaN;
1116
+ let tmEnd = this.endTime ? this.endTime.getTime() : NaN;
1117
+ if (isNaN(tmStart) || isNaN(tmEnd) || time < tmStart || time > tmEnd) return false;
1118
+ return true;
1119
+ }
1120
+ public get shouldBeOn(): boolean {
1121
+ let shouldBeOn = this._calcShouldBeOn(state.time.getTime());
1122
+ if (this.data.shouldBeOn !== shouldBeOn) this.setDataVal('shouldBeOn', shouldBeOn);
1123
+ return this.data.shouldBeOn || false;
1124
+ }
1125
+ protected set shouldBeOn(val: boolean) { this.setDataVal('shouldBeOn', val); }
1126
+ public get calculated(): boolean { return this.data.calculated; }
1127
+ public set calculated(val: boolean) { this.setDataVal('calculated', val); }
1128
+ public calcScheduleDate(ts: Timestamp, sched: Schedule): { startTime: Date, endTime: Date } {
1129
+ let times: { startTime: Date, endTime: Date } = { startTime: null, endTime: null };
1130
+ try {
1131
+ let sod = ts.clone().startOfDay();
1132
+ let ysod = ts.clone().addHours(-24).startOfDay();
1133
+ let nsod = ts.clone().addHours(-24).startOfDay();
1134
+ let ytimes: { startTime: Date, endTime: Date } = { startTime: null, endTime: null }; // Yesterday
1135
+ let ttimes: { startTime: Date, endTime: Date } = { startTime: null, endTime: null }; // Today
1136
+ let ntimes: { startTime: Date, endTime: Date } = { startTime: null, endTime: null }; // Tomorrow
1137
+ let tt = sys.board.valueMaps.scheduleTimeTypes.transform(sched.startTimeType);
1138
+ // Add the range for today and yesterday.
1139
+ switch (tt.name) {
1140
+ case 'sunrise':
1141
+ let sr = state.heliotrope.calcAdjustedTimes(sod.toDate(), 0, sched.startTimeOffset);
1142
+ ytimes.startTime = sr.prevSunrise;
1143
+ ttimes.startTime = sr.sunrise;
1144
+ ntimes.startTime = sr.nextSunrise;
1145
+ break;
1146
+ case 'sunset':
1147
+ let ss = state.heliotrope.calcAdjustedTimes(sod.toDate(), 0, sched.startTimeOffset);
1148
+ ytimes.startTime = ss.prevSunset;
1149
+ ttimes.startTime = ss.sunset;
1150
+ ntimes.startTime = ss.nextSunset;
1151
+ break;
1152
+ default:
1153
+ ytimes.startTime = ysod.clone().addMinutes(sched.startTime).toDate();
1154
+ ttimes.startTime = sod.clone().addMinutes(sched.startTime).toDate();
1155
+ ntimes.startTime = nsod.clone().addMinutes(sched.startTime).toDate();
1156
+ break;
1157
+ }
1158
+ tt = sys.board.valueMaps.scheduleTimeTypes.transform(sched.endTimeType);
1159
+ switch (tt.name) {
1160
+ case 'sunrise':
1161
+ let sr = state.heliotrope.calcAdjustedTimes(sod.toDate(), 0, sched.endTimeOffset);
1162
+ // If the start time of the previous window is greater than the previous sunrise then we use the sunrise for today.
1163
+ ytimes.endTime = ytimes.startTime >= sr.prevSunrise ? sr.sunrise : sr.prevSunrise;
1164
+ // If ths start time of the current window is greater than the current sunrise then we use the sunrise for tomorrow.
1165
+ ttimes.endTime = ttimes.startTime >= sr.sunrise ? sr.nextSunrise : sr.sunrise;
1166
+ ntimes.endTime = ntimes.startTime >= sr.nextSunrise ? new Timestamp(sr.nextSunrise).addHours(24).toDate() : sr.nextSunrise;
1167
+ break;
1168
+ case 'sunset':
1169
+ let ss = state.heliotrope.calcAdjustedTimes(sod.toDate(), 0, sched.endTimeOffset);
1170
+ // If the start time of the previous window is greater than the previous sunset then we use the sunset for today.
1171
+ ytimes.endTime = ytimes.startTime >= ss.prevSunset ? ss.sunset : ss.prevSunset;
1172
+ ttimes.endTime = ttimes.startTime >= ss.sunset ? ss.nextSunset : ss.nextSunset;
1173
+ ntimes.endTime = ntimes.startTime >= ss.nextSunset ? new Timestamp(ss.nextSunset).addHours(24).toDate() : ss.nextSunset;
1174
+ break;
1175
+ default:
1176
+ ytimes.endTime = ysod.clone().addMinutes(sched.endTime).toDate();
1177
+ if (ytimes.endTime <= ytimes.startTime) ytimes.endTime = ysod.clone().addHours(24).addMinutes(sched.endTime).toDate();
1178
+ ttimes.endTime = sod.clone().addMinutes(sched.endTime).toDate();
1179
+ if (ttimes.endTime <= ttimes.startTime) ttimes.endTime = sod.clone().addHours(24).addMinutes(sched.endTime).toDate();
1180
+ ntimes.endTime = nsod.clone().addMinutes(sched.endTime).toDate();
1181
+ if (ntimes.endTime <= ntimes.startTime) ntimes.endTime = nsod.clone().addHours(24).addMinutes(sched.endTime).toDate();
1182
+ break;
1183
+ }
1184
+ ttimes.startTime.setSeconds(0, 0); // Set the start time to the beginning of the minute.
1185
+ ttimes.endTime.setSeconds(59, 999); // Set the end time to the end of the minute.
1186
+ ytimes.startTime.setSeconds(0, 0);
1187
+ ytimes.endTime.setSeconds(59, 999);
1188
+ ntimes.startTime.setSeconds(0, 0);
1189
+ ntimes.endTime.setSeconds(59, 999);
1190
+ // Now check the dow for each range. If the start time for the dow matches then include it. If not then do not.
1191
+ let schedDays = sys.board.valueMaps.scheduleDays.toArray();
1192
+ let fnInRange = (time, times) => {
1193
+ let tmStart = times.startTime ? times.startTime.getTime() : NaN;
1194
+ let tmEnd = times.endTime ? times.endTime.getTime() : NaN;
1195
+ if (isNaN(tmStart) || isNaN(tmEnd) || time < tmStart || time > tmEnd) return false;
1196
+ return true;
1197
+ }
1198
+ let tm = ts.getTime();
1199
+ if (fnInRange(tm, ttimes)) {
1200
+ // Check the dow.
1201
+ let sd = schedDays.find(elem => elem.dow === ttimes.startTime.getDay());
1202
+ if (typeof sd !== 'undefined' && (sched.scheduleDays & sd.bitval) !== 0) {
1203
+ times.startTime = ttimes.startTime;
1204
+ times.endTime = ttimes.endTime;
1205
+ return times;
1206
+ }
1207
+ }
1208
+ // First check if we are still running yesterday. This will ensure we have
1209
+ // the first runtime.
1210
+ if (fnInRange(tm, ytimes)) {
1211
+ // Check the dow.
1212
+ let sd = schedDays.find(elem => elem.dow === ytimes.startTime.getDay());
1213
+ if (typeof sd !== 'undefined' && (sched.scheduleDays & sd.bitval) !== 0) {
1214
+ times.startTime = ytimes.startTime;
1215
+ times.endTime = ytimes.startTime;
1216
+ return times;
1217
+ }
1218
+ }
1219
+ // Then check if we are running today. If we have already run then get net next run
1220
+ // time.
1221
+ if (tm <= ttimes.startTime.getTime()) {
1222
+ let sd = schedDays.find(elem => elem.dow === ttimes.startTime.getDay());
1223
+ if (typeof sd !== 'undefined' && (sched.scheduleDays & sd.bitval) !== 0) {
1224
+ times.startTime = ttimes.startTime;
1225
+ times.endTime = ttimes.endTime;
1226
+ return times;
1227
+ }
1228
+ }
1229
+ // Then look for tomorrow.
1230
+ if (tm <= ntimes.startTime.getTime()) {
1231
+ let sd = schedDays.find(elem => elem.dow === ntimes.startTime.getDay());
1232
+ if (typeof sd !== 'undefined' && (sched.scheduleDays & sd.bitval) !== 0) {
1233
+ times.startTime = ntimes.startTime;
1234
+ times.endTime = ntimes.endTime;
1235
+ return times;
1236
+ }
1237
+ }
1238
+ return times;
1239
+ } catch (err) {
1240
+ logger.error(`Error calculating date for schedule ${sched.id}: ${err.message}`);
1241
+ }
1242
+ finally { return times; }
1243
+ }
1244
+ public calcSchedule(currentTime: Timestamp, sched: Schedule): boolean {
1245
+ try {
1246
+ let sod = currentTime.clone().startOfDay();
1247
+
1248
+ // There are 3 conditions where the schdedule will be recalculated. The first
1249
+ // 1. The calculated flag is false
1250
+ // 2. The calculated flag is true and the calculated date < the current start of day
1251
+ // 3. Regardless of the calculated date the current end time has passed and the start time is
1252
+ // from a prior date. This will happen when the schedule is complete and we need to calculate the
1253
+ // next run time.
1254
+ let dtCalc = typeof this.calculatedDate !== 'undefined' && typeof this.calculatedDate.getTime === 'function' ? new Date(this.calculatedDate.getTime()).setHours(0, 0, 0, 0) : new Date(1970, 0, 1, 0, 0).getTime();
1255
+ let recalc = !this.calculated;
1256
+ if (!recalc && sod.getTime() !== dtCalc) recalc = true;
1257
+ if (!recalc && (this.endTime.getTime() < new Date().getTime() && this.startTime.getTime() < dtCalc)) {
1258
+ recalc = true;
1259
+ logger.info(`Recalculating expired schedule ${sched.id}`);
1260
+ }
1261
+ if (!recalc) return this.shouldBeOn;
1262
+ //if (this.calculated && sod.getTime() === dtCalc) return this.shouldBeOn;
1263
+ this.calculatedDate = new Date(new Date().setHours(0, 0, 0, 0));
1264
+ if (sched.isActive === false || sched.disabled) return false;
1265
+ let tt = sys.board.valueMaps.scheduleTimeTypes.transform(sched.startTimeType);
1266
+ // If this is a runonce schedule we need to check for the rundate
1267
+ let type = sys.board.valueMaps.scheduleTypes.transform(sched.scheduleType);
1268
+ let times = type.name === 'runonce' ? this.calcScheduleDate(new Timestamp(sched.startDate), sched) : this.calcScheduleDate(state.time.clone(), sched);
1269
+ if (times.startTime && times.endTime.getTime() > currentTime.getTime()) {
1270
+ // Check to see if it should be on.
1271
+ this.startTime = times.startTime;
1272
+ this.endTime = times.endTime;
1273
+ this.calculated = true;
1274
+ return this.shouldBeOn;
1275
+ }
1276
+ else {
1277
+ // Chances are that the current dow is not valid. Fast forward until we get a day that works. That will
1278
+ // be the next scheduled run date.
1279
+ if (type.name !== 'runonce' && sched.scheduleDays > 0) {
1280
+ let schedDays = sys.board.valueMaps.scheduleDays.toArray();
1281
+ let day = sod.clone().addHours(24);
1282
+ let dow = day.getDay();
1283
+ while (dow !== sod.getDay()) {
1284
+ let sd = schedDays.find(elem => elem.dow === day.getDay());
1285
+ if (typeof sd !== 'undefined' && (sched.scheduleDays & sd.bitval) !== 0) {
1286
+ times = this.calcScheduleDate(day, sched);
1287
+ break;
1288
+ }
1289
+ else day.addHours(24);
1290
+ }
1291
+ }
1292
+ this.startTime = times.startTime;
1293
+ this.endTime = times.endTime;
1294
+ this.calculated = true;
1295
+ }
1296
+ return this.shouldBeOn;
1297
+ } catch (err) {
1298
+ this.calculated = true;
1299
+ this.calculatedDate = new Date(new Date().setHours(0, 0, 0, 0));
1300
+ this.startTime = null;
1301
+ this.endTime = null;
1302
+ }
1303
+ }
1067
1304
  }
1068
1305
  export class ScheduleState extends EqState {
1069
1306
  constructor(data: any, dataName?: string) { super(data, dataName); }
@@ -1092,10 +1329,17 @@ export class ScheduleState extends EqState {
1092
1329
  public set startTime(val: number) { this.setDataVal('startTime', val); }
1093
1330
  public get endTime(): number { return this.data.endTime; }
1094
1331
  public set endTime(val: number) { this.setDataVal('endTime', val); }
1332
+ public get startTimeOffset(): number { return this.data.startTimeOffset || 0; }
1333
+ public set startTimeOffset(val: number) { this.setDataVal('startTimeOffset', val); }
1334
+ public get endTimeOffset(): number { return this.data.endTimeOffset || 0; }
1335
+ public set endTimeOffset(val: number) { this.setDataVal('endTimeOffset', val); }
1336
+
1095
1337
  public get circuit(): number { return this.data.circuit; }
1096
1338
  public set circuit(val: number) { this.setDataVal('circuit', val); }
1097
1339
  public get disabled(): boolean { return this.data.disabled; }
1098
1340
  public set disabled(val: boolean) { this.setDataVal('disabled', val); }
1341
+ public get triggered(): boolean { return this.data.triggered || false; }
1342
+ public set triggered(val: boolean) { this.setDataVal('triggered', val); }
1099
1343
  public get scheduleType(): number { return typeof (this.data.scheduleType) !== 'undefined' ? this.data.scheduleType.val : undefined; }
1100
1344
  public set scheduleType(val: number) {
1101
1345
  if (this.scheduleType !== val) {
@@ -1149,11 +1393,18 @@ export class ScheduleState extends EqState {
1149
1393
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1150
1394
  public get manualPriorityActive(): boolean { return this.data.manualPriorityActive; }
1151
1395
  public set manualPriorityActive(val: boolean) { this.setDataVal('manualPriorityActive', val); }
1396
+ public get scheduleTime(): ScheduleTime { return new ScheduleTime(this.data, 'scheduleTime', this); }
1397
+ public recalculate(force?: boolean) {
1398
+ if (force === true) this.scheduleTime.calculated = false;
1399
+ this.scheduleTime.calcSchedule(state.time, sys.schedules.getItemById(this.id));
1400
+ }
1152
1401
  public getExtended() {
1153
1402
  let sched = this.get(true); // Always operate on a copy.
1154
1403
  //if (typeof this.circuit !== 'undefined')
1155
1404
  sched.circuit = state.circuits.getInterfaceById(this.circuit).get(true);
1156
- //else sched.circuit = {};
1405
+ sched.clockMode = sys.board.valueMaps.clockModes.transform(sys.general.options.clockMode) || {};
1406
+ //let times = this.calcScheduleTimes(sched);
1407
+ //sched.times = { shouldBeOn: times.shouldBeOn, startTime: times.shouldBeOn ? Timestamp.toISOLocal(times.startTime) : '', endTime: times.shouldBeOn ? Timestamp.toISOLocal(times.endTime) : '' };
1157
1408
  return sched;
1158
1409
  }
1159
1410
  public emitEquipmentChange() {
@@ -1224,6 +1475,8 @@ export class CircuitGroupState extends EqState implements ICircuitGroupState, IC
1224
1475
  }
1225
1476
  public get isOn(): boolean { return this.data.isOn; }
1226
1477
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1478
+ public get priority(): string { return this.data.priority || 'manual' }
1479
+ public set priority(val: string) { this.setDataVal('priority', val); }
1227
1480
  public get endTime(): Timestamp {
1228
1481
  if (typeof this.data.endTime === 'undefined') return undefined;
1229
1482
  return new Timestamp(this.data.endTime);
@@ -1313,6 +1566,8 @@ export class LightGroupState extends EqState implements ICircuitGroupState, ICir
1313
1566
  this.hasChanged = true;
1314
1567
  }
1315
1568
  }
1569
+ public get priority(): string { return this.data.priority || 'manual' }
1570
+ public set priority(val: string) { this.setDataVal('priority', val); }
1316
1571
  public get endTime(): Timestamp {
1317
1572
  if (typeof this.data.endTime === 'undefined') return undefined;
1318
1573
  return new Timestamp(this.data.endTime);
@@ -1578,6 +1833,12 @@ export class HeaterState extends EqState {
1578
1833
  this.hasChanged = true;
1579
1834
  }
1580
1835
  }
1836
+ public get prevHeaterOffTemp(): number { return this.data.prevHeaterOffTemp; }
1837
+ public set prevHeaterOffTemp(val: number) {
1838
+ if (this.prevHeaterOffTemp !== val) {
1839
+ this.data.prevHeaterOffTemp = val;
1840
+ }
1841
+ }
1581
1842
  public get startupDelay(): boolean { return this.data.startupDelay; }
1582
1843
  public set startupDelay(val: boolean) { this.setDataVal('startupDelay', val); }
1583
1844
  public get shutdownDelay(): boolean { return this.data.shutdownDelay; }
@@ -1630,6 +1891,8 @@ export class FeatureState extends EqState implements ICircuitState {
1630
1891
  }
1631
1892
  public get showInFeatures(): boolean { return this.data.showInFeatures; }
1632
1893
  public set showInFeatures(val: boolean) { this.setDataVal('showInFeatures', val); }
1894
+ public get priority(): string { return this.data.priority || 'manual' }
1895
+ public set priority(val: string) { this.setDataVal('priority', val); }
1633
1896
  public get endTime(): Timestamp {
1634
1897
  if (typeof this.data.endTime === 'undefined') return undefined;
1635
1898
  return new Timestamp(this.data.endTime);
@@ -1654,6 +1917,8 @@ export class VirtualCircuitState extends EqState implements ICircuitState {
1654
1917
  public set nameId(val: number) { this.setDataVal('nameId', val); }
1655
1918
  public get isOn(): boolean { return this.data.isOn; }
1656
1919
  public set isOn(val: boolean) { this.setDataVal('isOn', val); }
1920
+ public get priority(): string { return 'manual' } // These are always manual priority
1921
+ public set priority(val: string) { ; }
1657
1922
  public get type() { return typeof this.data.type !== 'undefined' ? this.data.type.val : -1; }
1658
1923
  public set type(val: number) {
1659
1924
  if (this.type !== val) {
@@ -1729,6 +1994,8 @@ export class CircuitState extends EqState implements ICircuitState {
1729
1994
  this.hasChanged = true;
1730
1995
  }
1731
1996
  }
1997
+ public get priority(): string { return this.data.priority; }
1998
+ public set priority(val: string) { this.setDataVal('priority', val); }
1732
1999
  public get showInFeatures(): boolean { return this.data.showInFeatures; }
1733
2000
  public set showInFeatures(val: boolean) { this.setDataVal('showInFeatures', val); }
1734
2001
  public get isOn(): boolean { return this.data.isOn; }
@@ -1762,6 +2029,8 @@ export class CircuitState extends EqState implements ICircuitState {
1762
2029
  this.hasChanged = true;
1763
2030
  }
1764
2031
  }
2032
+ public get scheduled(): boolean { return this.data.scheduled || false }
2033
+ public set scheduled(val: boolean) { this.setDataVal('scheduled', val); }
1765
2034
  public get startTime(): Timestamp {
1766
2035
  if (typeof this.data.startTime === 'undefined') return undefined;
1767
2036
  return new Timestamp(this.data.startTime);
@@ -2364,6 +2633,7 @@ export class ChemDoserState extends EqState implements IChemicalState, IChemCont
2364
2633
 
2365
2634
  export class ChemControllerState extends EqState implements IChemControllerState {
2366
2635
  public initData() {
2636
+ if (typeof this.activeBodyId === 'undefined') this.data.activeBodyId = 0;
2367
2637
  if (typeof this.data.saturationIndex === 'undefined') this.data.saturationIndex = 0;
2368
2638
  if (typeof this.data.flowDetected === 'undefined') this.data.flowDetected = false;
2369
2639
  if (typeof this.data.orp === 'undefined') this.data.orp = {};
@@ -2467,6 +2737,8 @@ export class ChemControllerState extends EqState implements IChemControllerState
2467
2737
  public set address(val: number) { this.setDataVal('address', val); }
2468
2738
  public get isBodyOn(): boolean { return this.data.isBodyOn; }
2469
2739
  public set isBodyOn(val: boolean) { this.data.isBodyOn = val; }
2740
+ public get activeBodyId(): number { return this.data.activeBodyId || 0; }
2741
+ public set activeBodyId(val: number) { this.data.activeBodyId = val; }
2470
2742
  public get flowDetected(): boolean { return this.data.flowDetected; }
2471
2743
  public set flowDetected(val: boolean) { this.data.flowDetected = val; }
2472
2744
  public get status(): number {
@@ -3449,10 +3721,11 @@ export class FilterState extends EqState {
3449
3721
  this.hasChanged = true;
3450
3722
  }
3451
3723
  }
3452
- public get pressureUnits(): number { return this.data.pressureUnits; }
3724
+ public get pressureUnits(): number { return typeof this.data.pressureUnits === 'undefined' ? 0 : this.data.pressureUnits.val; }
3453
3725
  public set pressureUnits(val: number) {
3454
- if (this.pressureUnits !== val) {
3726
+ if (this.pressureUnits !== val || typeof this.data.pressureUnits === 'undefined') {
3455
3727
  this.setDataVal('pressureUnits', sys.board.valueMaps.pressureUnits.transform(val));
3728
+ this.hasChanged = true;
3456
3729
  }
3457
3730
  }
3458
3731
  public get pressure(): number { return this.data.pressure; }