nodejs-poolcontroller 7.6.0 → 7.6.1

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 (85) hide show
  1. package/.eslintrc.json +44 -44
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +52 -52
  3. package/CONTRIBUTING.md +74 -74
  4. package/Changelog +215 -215
  5. package/Dockerfile +17 -17
  6. package/Gruntfile.js +40 -40
  7. package/LICENSE +661 -661
  8. package/README.md +191 -186
  9. package/app.ts +0 -0
  10. package/config/Config.ts +24 -2
  11. package/config/VersionCheck.ts +27 -12
  12. package/config copy.json +299 -299
  13. package/controller/Constants.ts +0 -0
  14. package/controller/Equipment.ts +2459 -2405
  15. package/controller/Errors.ts +180 -180
  16. package/controller/Lockouts.ts +436 -422
  17. package/controller/State.ts +51 -26
  18. package/controller/boards/BoardFactory.ts +45 -45
  19. package/controller/boards/EasyTouchBoard.ts +2653 -2537
  20. package/controller/boards/IntelliCenterBoard.ts +4230 -4034
  21. package/controller/boards/IntelliComBoard.ts +63 -63
  22. package/controller/boards/IntelliTouchBoard.ts +241 -241
  23. package/controller/boards/NixieBoard.ts +1675 -1660
  24. package/controller/boards/SystemBoard.ts +4697 -4463
  25. package/controller/comms/Comms.ts +138 -1
  26. package/controller/comms/messages/Messages.ts +3 -5
  27. package/controller/comms/messages/config/ChlorinatorMessage.ts +1 -1
  28. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  29. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  30. package/controller/comms/messages/config/ConfigMessage.ts +0 -0
  31. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  32. package/controller/comms/messages/config/CustomNameMessage.ts +30 -30
  33. package/controller/comms/messages/config/EquipmentMessage.ts +0 -0
  34. package/controller/comms/messages/config/ExternalMessage.ts +9 -7
  35. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  36. package/controller/comms/messages/config/GeneralMessage.ts +0 -0
  37. package/controller/comms/messages/config/HeaterMessage.ts +0 -20
  38. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  39. package/controller/comms/messages/config/OptionsMessage.ts +25 -1
  40. package/controller/comms/messages/config/PumpMessage.ts +0 -0
  41. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  42. package/controller/comms/messages/config/ScheduleMessage.ts +347 -342
  43. package/controller/comms/messages/config/SecurityMessage.ts +0 -0
  44. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  45. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  46. package/controller/comms/messages/status/EquipmentStateMessage.ts +1 -1
  47. package/controller/comms/messages/status/HeaterStateMessage.ts +86 -86
  48. package/controller/comms/messages/status/IntelliChemStateMessage.ts +445 -397
  49. package/controller/comms/messages/status/IntelliValveStateMessage.ts +35 -35
  50. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  51. package/controller/comms/messages/status/VersionMessage.ts +0 -0
  52. package/controller/nixie/Nixie.ts +162 -162
  53. package/controller/nixie/NixieEquipment.ts +103 -103
  54. package/controller/nixie/bodies/Body.ts +120 -120
  55. package/controller/nixie/bodies/Filter.ts +135 -135
  56. package/controller/nixie/chemistry/ChemController.ts +2498 -2398
  57. package/controller/nixie/chemistry/Chlorinator.ts +314 -314
  58. package/controller/nixie/circuits/Circuit.ts +248 -245
  59. package/controller/nixie/heaters/Heater.ts +648 -600
  60. package/controller/nixie/pumps/Pump.ts +661 -661
  61. package/controller/nixie/schedules/Schedule.ts +257 -257
  62. package/controller/nixie/valves/Valve.ts +170 -170
  63. package/defaultConfig.json +286 -286
  64. package/issue_template.md +51 -51
  65. package/logger/DataLogger.ts +448 -448
  66. package/logger/Logger.ts +0 -0
  67. package/package.json +56 -56
  68. package/tsconfig.json +25 -25
  69. package/web/Server.ts +2 -2
  70. package/web/bindings/influxDB.json +1021 -981
  71. package/web/bindings/mqtt.json +654 -654
  72. package/web/bindings/mqttAlt.json +684 -684
  73. package/web/bindings/rulesManager.json +54 -54
  74. package/web/bindings/smartThings-Hubitat.json +31 -31
  75. package/web/bindings/valveRelays.json +20 -20
  76. package/web/bindings/vera.json +25 -25
  77. package/web/interfaces/baseInterface.ts +136 -136
  78. package/web/interfaces/httpInterface.ts +124 -124
  79. package/web/interfaces/influxInterface.ts +245 -241
  80. package/web/interfaces/mqttInterface.ts +475 -475
  81. package/web/services/config/Config.ts +10 -108
  82. package/web/services/config/ConfigSocket.ts +0 -0
  83. package/web/services/state/State.ts +71 -4
  84. package/web/services/state/StateSocket.ts +0 -0
  85. package/web/services/utilities/Utilities.ts +42 -42
@@ -1,258 +1,258 @@
1
- import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, ParameterOutOfRangeError } from '../../Errors';
2
- import { utils, Timestamp } from '../../Constants';
3
- import { logger } from '../../../logger/Logger';
4
-
5
- import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
6
- import { Schedule, ScheduleCollection, sys } from "../../../controller/Equipment";
7
- import { ScheduleState, state, } from "../../State";
8
- import { setTimeout, clearTimeout } from 'timers';
9
- import { NixieControlPanel } from '../Nixie';
10
- import { webApp, InterfaceServerResponse } from "../../../web/Server";
11
-
12
- export class NixieScheduleCollection extends NixieEquipmentCollection<NixieSchedule> {
13
- public async setScheduleAsync(schedule: Schedule, data: any) {
14
- // By the time we get here we know that we are in control and this is a schedule we should be in control of.
15
- try {
16
- let c: NixieSchedule = this.find(elem => elem.id === schedule.id) as NixieSchedule;
17
- if (typeof c === 'undefined') {
18
- schedule.master = 1;
19
- c = new NixieSchedule(this.controlPanel, schedule);
20
- this.push(c);
21
- await c.setScheduleAsync(data);
22
- logger.info(`A Schedule was not found for id #${schedule.id} creating Schedule`);
23
- }
24
- else {
25
- await c.setScheduleAsync(data);
26
- }
27
- }
28
- catch (err) { logger.error(`setScheduleAsync: ${err.message}`); return Promise.reject(err); }
29
- }
30
- public async initAsync(schedules: ScheduleCollection) {
31
- try {
32
- for (let i = 0; i < schedules.length; i++) {
33
- let schedule = schedules.getItemByIndex(i);
34
- if (schedule.master === 1) {
35
- if (typeof this.find(elem => elem.id === schedule.id) === 'undefined') {
36
- logger.info(`Initializing Schedule ${schedule.id}`);
37
- let nSchedule = new NixieSchedule(this.controlPanel, schedule);
38
- this.push(nSchedule);
39
- }
40
- }
41
- }
42
- }
43
- catch (err) { logger.error(`Nixie Schedule initAsync: ${err.message}`); return Promise.reject(err); }
44
- }
45
- public async triggerSchedules() {
46
- try {
47
- let ctx = new NixieScheduleContext();
48
- for (let i = 0; i < this.length; i++) {
49
- (this[i] as NixieSchedule).triggerScheduleAsync(ctx);
50
- }
51
- // Set the heat modes for the bodies.
52
- for (let i = 0; i < ctx.heatModes.length; i++) {
53
- let mode = ctx.heatModes[i];
54
- let body = sys.bodies.getItemById(mode.id);
55
- await sys.board.bodies.setHeatModeAsync(sys.bodies.getItemById(mode.id), mode.heatMode);
56
- if (typeof mode.heatSetpoint !== 'undefined') await sys.board.bodies.setHeatSetpointAsync(body, mode.heatSetpoint);
57
- if (typeof mode.coolSetpoint !== 'undefined') await sys.board.bodies.setCoolSetpointAsync(body, mode.coolSetpoint);
58
- }
59
- // Alright now that we are done with that we need to set all the circuit states that need changing.
60
- for (let i = 0; i < ctx.circuits.length; i++) {
61
- let circuit = ctx.circuits[i];
62
- await sys.board.circuits.setCircuitStateAsync(circuit.id, circuit.isOn);
63
- }
64
- } catch (err) { logger.error(`Error triggering schedules: ${err}`); }
65
- }
66
- }
67
- export class NixieSchedule extends NixieEquipment {
68
- public pollingInterval: number = 10000;
69
- private _pollTimer: NodeJS.Timeout = null;
70
- public schedule: Schedule;
71
- private suspended: boolean = false;
72
- private running: boolean = false;
73
- constructor(ncp: INixieControlPanel, schedule: Schedule) {
74
- super(ncp);
75
- this.schedule = schedule;
76
- this.pollEquipmentAsync();
77
- }
78
- public get id(): number { return typeof this.schedule !== 'undefined' ? this.schedule.id : -1; }
79
- public async setScheduleAsync(data: any) {
80
- try {
81
- let schedule = this.schedule;
82
- }
83
- catch (err) { logger.error(`Nixie setScheduleAsync: ${err.message}`); return Promise.reject(err); }
84
- }
85
- public async pollEquipmentAsync() {
86
- let self = this;
87
- try {
88
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
89
- this._pollTimer = null;
90
- let success = false;
91
- }
92
- catch (err) { logger.error(`Nixie Error polling Schedule - ${err}`); }
93
- finally { this._pollTimer = setTimeout(async () => await self.pollEquipmentAsync(), this.pollingInterval || 10000); }
94
- }
95
- public async validateSetupAsync(Schedule: Schedule, temp: ScheduleState) {
96
- try {
97
- // The validation will be different if the Schedule is on or not. So lets get that information.
98
- } catch (err) { logger.error(`Nixie Error checking Schedule Hardware ${this.schedule.id}: ${err.message}`); return Promise.reject(err); }
99
- }
100
- public async triggerScheduleAsync(ctx: NixieScheduleContext) {
101
- try {
102
- if (this.schedule.isActive === false) return;
103
- let ssched = state.schedules.getItemById(this.id, true);
104
- // RULES FOR NIXIE SCHEDULES
105
- // ------------------------------------------------------
106
- // Schedules can be overridden so it is important that when the
107
- // state is changed for the schedule if it is currently active that
108
- // Nixie does not override the state of the scheduled circuit or feature.
109
- // 1. If the feature happens to be running and the schedule is not yet turned on then
110
- // it should not override what the user says.
111
- // 2. If a schedule is running and the state of the circuit changes to off then the new state should suspend the schedule
112
- // until which time the feature is turned back on again. Then the off time will come into play.
113
- // 3. Egg timers will be managed by the individual circuit. If this is being turned on via the schedule then
114
- // the egg timer is not in effect.
115
- // 4. If there are overlapping schedules, then the off date is determined by
116
- // the maximum off date.
117
- // 5. If a schedule should be on and the user turns the schedule off then the schedule expires until such time
118
- // as the time off has expired. When that occurs the schedule should be reset to run at the designated time. If the
119
- // user resets the schedule by turning the circuit back on again then the schedule will resume and turn off at the specified
120
- // time.
121
- // 6. Heat setpoints should only be changed when the schedule is first turning on the scheduled circuit.
122
- let cstate = state.circuits.getInterfaceById(this.schedule.circuit, false);
123
- let circuit = sys.circuits.getInterfaceById(this.schedule.circuit, false, { isActive: false });
124
- if (circuit.isActive === false) {
125
- ssched.isOn = false;
126
- return;
127
- }
128
- let shouldBeOn = this.shouldBeOn(ssched); // This should also set the validity for the schedule if there are errors.
129
- //console.log(`Processing schedule ${this.schedule.id} - ${circuit.name} : ShouldBeOn: ${shouldBeOn} Suspended: ${this.suspended} Running: ${this.running}`);
130
- // COND 1: The schedule should be on and the schedule is not yet on.
131
- if (shouldBeOn && !this.running && !this.suspended) {
132
- // If the circuit is on then we need to clear the suspended flag and set the running flag.
133
- if (cstate.isOn) {
134
- // If the suspended flag was previously on then we need to clear it
135
- // because the user turned it back on.
136
- this.suspended = false;
137
- }
138
- ctx.setCircuit(circuit.id, true);
139
- // Alright we are turning on the circuit. If these are body circuits then we need to determine
140
- // whether we will be setting the setpoints/heatmode on the body.
141
- let body = sys.bodies.find(elem => elem.circuit === circuit.id);
142
- if (typeof body !== 'undefined') {
143
- let heatSource = sys.board.valueMaps.heatSources.transform(this.schedule.heatSource);
144
- if (heatSource.name !== 'nochange') {
145
- switch (heatSource.name) {
146
- case 'nochange':
147
- case 'dontchange':
148
- break;
149
- case 'off':
150
- ctx.setHeatMode(body.id, 'off');
151
- break;
152
- default:
153
- ctx.setHeatMode(body.id, heatSource.name, this.schedule.heatSetpoint, heatSource.hasCoolSetpoint ? this.schedule.coolSetpoint : undefined);
154
- break;
155
- }
156
- }
157
- }
158
- ssched.isOn = true;
159
- this.running = true;
160
- }
161
- else if (shouldBeOn && this.running) {
162
- // We do nothing here.
163
- this.suspended = !cstate.isOn;
164
- }
165
- // Our schedule has expired it is time to turn it off.
166
- else if (!shouldBeOn) {
167
- // Turn this sucker off. But wait if there is an overlapping schedule then we should
168
- // not turn it off. We will need some logic to deal with this.
169
- if (this.running) ctx.setCircuit(circuit.id, false);
170
- ssched.isOn = false;
171
- this.running = false;
172
- this.suspended = false;
173
- }
174
- if (!shouldBeOn && ssched.isOn === true) {
175
- // Turn off the circuit.
176
- ctx.setCircuit(circuit.id, false);
177
- ssched.isOn = false;
178
- }
179
- ssched.emitEquipmentChange();
180
- } catch (err) { logger.error(`Error processing schedule: ${err.message}`); }
181
-
182
- }
183
- protected calcTime(dt: Timestamp, type: number, offset: number): Timestamp {
184
- let tt = sys.board.valueMaps.scheduleTimeTypes.transform(type);
185
- switch (tt.name) {
186
- case 'sunrise':
187
- return new Timestamp(state.heliotrope.sunrise);
188
- case 'sunset':
189
- return new Timestamp(state.heliotrope.sunset);
190
- default:
191
- return dt.startOfDay().addMinutes(offset);
192
- }
193
- }
194
- protected shouldBeOn(sstate: ScheduleState): boolean {
195
- if (this.schedule.isActive === false) return false;
196
- // Be careful with toDate since this returns a mutable date object from the state timestamp. startOfDay makes it immutable.
197
- let sod = state.time.startOfDay()
198
- let dow = sod.toDate().getDay();
199
- let type = sys.board.valueMaps.scheduleTypes.transform(this.schedule.scheduleType);
200
- if (type.name === 'runonce') {
201
- // If we are not matching up with the day then we shouldn't be running.
202
- if (sod.fullYear !== this.schedule.startYear || sod.month + 1 !== this.schedule.startMonth || sod.date !== this.schedule.startDay) return false;
203
- }
204
- else {
205
- // Convert the dow to the bit value.
206
- let sd = sys.board.valueMaps.scheduleDays.toArray().find(elem => elem.dow === dow);
207
- let dayVal = sd.bitVal || sd.val; // The bitval allows mask overrides.
208
- // First check to see if today is one of our days.
209
- if ((this.schedule.scheduleDays & dayVal) === 0) return false;
210
- }
211
- // Next normalize our start and end times. Fortunately, the start and end times are normalized here so that
212
- // [0, {name: 'manual', desc: 'Manual }]
213
- // [1, { name: 'sunrise', desc: 'Sunrise' }],
214
- // [2, { name: 'sunset', desc: 'Sunset' }]
215
- let tmStart = this.calcTime(sod, this.schedule.startTime, this.schedule.startTime).getTime();
216
- let tmEnd = this.calcTime(sod, this.schedule.endTimeType, this.schedule.endTime).getTime();
217
-
218
- if (isNaN(tmStart)) return false;
219
- if (isNaN(tmEnd)) return false;
220
- // If we are past our window we should be off.
221
- let tm = state.time.getTime();
222
- if (tm >= tmEnd) return false;
223
- if (tm <= tmStart) return false;
224
-
225
- // If we make it here we should be on.
226
- return true;
227
- }
228
- public async closeAsync() {
229
- try {
230
- if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
231
- this._pollTimer = null;
232
- }
233
- catch (err) { logger.error(`Nixie Schedule closeAsync: ${err.message}`); return Promise.reject(err); }
234
- }
235
- public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
236
- }
237
- class NixieScheduleContext {
238
- constructor() {
239
-
240
- }
241
- public circuits: { id: number, isOn: boolean }[] = [];
242
- public heatModes: { id: number, heatMode: number, heatSetpoint?: number, coolSetpoint?: number }[] = [];
243
- public setCircuit(id: number, isOn: boolean) {
244
- let c = this.circuits.find(elem => elem.id === id);
245
- if (typeof c === 'undefined') this.circuits.push({ id: id, isOn: isOn });
246
- else c.isOn = isOn;
247
- }
248
- public setHeatMode(id: number, heatMode: string, heatSetpoint?: number, coolSetpoint?: number) {
249
- let mode = sys.board.valueMaps.heatModes.transformByName(heatMode);
250
- let hm = this.heatModes.find(elem => elem.id == id);
251
- if (typeof hm === 'undefined') this.heatModes.push({ id: id, heatMode: mode.val, heatSetpoint: heatSetpoint, coolSetpoint: coolSetpoint });
252
- else {
253
- hm.heatMode = mode.val;
254
- if (typeof heatSetpoint !== 'undefined') hm.heatSetpoint = heatSetpoint;
255
- if (typeof coolSetpoint !== 'undefined') hm.coolSetpoint = coolSetpoint;
256
- }
257
- }
1
+ import { EquipmentNotFoundError, InvalidEquipmentDataError, InvalidEquipmentIdError, ParameterOutOfRangeError } from '../../Errors';
2
+ import { utils, Timestamp } from '../../Constants';
3
+ import { logger } from '../../../logger/Logger';
4
+
5
+ import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
6
+ import { Schedule, ScheduleCollection, sys } from "../../../controller/Equipment";
7
+ import { ScheduleState, state, } from "../../State";
8
+ import { setTimeout, clearTimeout } from 'timers';
9
+ import { NixieControlPanel } from '../Nixie';
10
+ import { webApp, InterfaceServerResponse } from "../../../web/Server";
11
+
12
+ export class NixieScheduleCollection extends NixieEquipmentCollection<NixieSchedule> {
13
+ public async setScheduleAsync(schedule: Schedule, data: any) {
14
+ // By the time we get here we know that we are in control and this is a schedule we should be in control of.
15
+ try {
16
+ let c: NixieSchedule = this.find(elem => elem.id === schedule.id) as NixieSchedule;
17
+ if (typeof c === 'undefined') {
18
+ schedule.master = 1;
19
+ c = new NixieSchedule(this.controlPanel, schedule);
20
+ this.push(c);
21
+ await c.setScheduleAsync(data);
22
+ logger.info(`A Schedule was not found for id #${schedule.id} creating Schedule`);
23
+ }
24
+ else {
25
+ await c.setScheduleAsync(data);
26
+ }
27
+ }
28
+ catch (err) { logger.error(`setScheduleAsync: ${err.message}`); return Promise.reject(err); }
29
+ }
30
+ public async initAsync(schedules: ScheduleCollection) {
31
+ try {
32
+ for (let i = 0; i < schedules.length; i++) {
33
+ let schedule = schedules.getItemByIndex(i);
34
+ if (schedule.master === 1) {
35
+ if (typeof this.find(elem => elem.id === schedule.id) === 'undefined') {
36
+ logger.info(`Initializing Schedule ${schedule.id}`);
37
+ let nSchedule = new NixieSchedule(this.controlPanel, schedule);
38
+ this.push(nSchedule);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ catch (err) { logger.error(`Nixie Schedule initAsync: ${err.message}`); return Promise.reject(err); }
44
+ }
45
+ public async triggerSchedules() {
46
+ try {
47
+ let ctx = new NixieScheduleContext();
48
+ for (let i = 0; i < this.length; i++) {
49
+ (this[i] as NixieSchedule).triggerScheduleAsync(ctx);
50
+ }
51
+ // Set the heat modes for the bodies.
52
+ for (let i = 0; i < ctx.heatModes.length; i++) {
53
+ let mode = ctx.heatModes[i];
54
+ let body = sys.bodies.getItemById(mode.id);
55
+ await sys.board.bodies.setHeatModeAsync(sys.bodies.getItemById(mode.id), mode.heatMode);
56
+ if (typeof mode.heatSetpoint !== 'undefined') await sys.board.bodies.setHeatSetpointAsync(body, mode.heatSetpoint);
57
+ if (typeof mode.coolSetpoint !== 'undefined') await sys.board.bodies.setCoolSetpointAsync(body, mode.coolSetpoint);
58
+ }
59
+ // Alright now that we are done with that we need to set all the circuit states that need changing.
60
+ for (let i = 0; i < ctx.circuits.length; i++) {
61
+ let circuit = ctx.circuits[i];
62
+ await sys.board.circuits.setCircuitStateAsync(circuit.id, circuit.isOn);
63
+ }
64
+ } catch (err) { logger.error(`Error triggering schedules: ${err}`); }
65
+ }
66
+ }
67
+ export class NixieSchedule extends NixieEquipment {
68
+ public pollingInterval: number = 10000;
69
+ private _pollTimer: NodeJS.Timeout = null;
70
+ public schedule: Schedule;
71
+ private suspended: boolean = false;
72
+ private running: boolean = false;
73
+ constructor(ncp: INixieControlPanel, schedule: Schedule) {
74
+ super(ncp);
75
+ this.schedule = schedule;
76
+ this.pollEquipmentAsync();
77
+ }
78
+ public get id(): number { return typeof this.schedule !== 'undefined' ? this.schedule.id : -1; }
79
+ public async setScheduleAsync(data: any) {
80
+ try {
81
+ let schedule = this.schedule;
82
+ }
83
+ catch (err) { logger.error(`Nixie setScheduleAsync: ${err.message}`); return Promise.reject(err); }
84
+ }
85
+ public async pollEquipmentAsync() {
86
+ let self = this;
87
+ try {
88
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
89
+ this._pollTimer = null;
90
+ let success = false;
91
+ }
92
+ catch (err) { logger.error(`Nixie Error polling Schedule - ${err}`); }
93
+ finally { this._pollTimer = setTimeout(async () => await self.pollEquipmentAsync(), this.pollingInterval || 10000); }
94
+ }
95
+ public async validateSetupAsync(Schedule: Schedule, temp: ScheduleState) {
96
+ try {
97
+ // The validation will be different if the Schedule is on or not. So lets get that information.
98
+ } catch (err) { logger.error(`Nixie Error checking Schedule Hardware ${this.schedule.id}: ${err.message}`); return Promise.reject(err); }
99
+ }
100
+ public async triggerScheduleAsync(ctx: NixieScheduleContext) {
101
+ try {
102
+ if (this.schedule.isActive === false) return;
103
+ let ssched = state.schedules.getItemById(this.id, true);
104
+ // RULES FOR NIXIE SCHEDULES
105
+ // ------------------------------------------------------
106
+ // Schedules can be overridden so it is important that when the
107
+ // state is changed for the schedule if it is currently active that
108
+ // Nixie does not override the state of the scheduled circuit or feature.
109
+ // 1. If the feature happens to be running and the schedule is not yet turned on then
110
+ // it should not override what the user says.
111
+ // 2. If a schedule is running and the state of the circuit changes to off then the new state should suspend the schedule
112
+ // until which time the feature is turned back on again. Then the off time will come into play.
113
+ // 3. Egg timers will be managed by the individual circuit. If this is being turned on via the schedule then
114
+ // the egg timer is not in effect.
115
+ // 4. If there are overlapping schedules, then the off date is determined by
116
+ // the maximum off date.
117
+ // 5. If a schedule should be on and the user turns the schedule off then the schedule expires until such time
118
+ // as the time off has expired. When that occurs the schedule should be reset to run at the designated time. If the
119
+ // user resets the schedule by turning the circuit back on again then the schedule will resume and turn off at the specified
120
+ // time.
121
+ // 6. Heat setpoints should only be changed when the schedule is first turning on the scheduled circuit.
122
+ let cstate = state.circuits.getInterfaceById(this.schedule.circuit, false);
123
+ let circuit = sys.circuits.getInterfaceById(this.schedule.circuit, false, { isActive: false });
124
+ if (circuit.isActive === false) {
125
+ ssched.isOn = false;
126
+ return;
127
+ }
128
+ let shouldBeOn = this.shouldBeOn(ssched); // This should also set the validity for the schedule if there are errors.
129
+ //console.log(`Processing schedule ${this.schedule.id} - ${circuit.name} : ShouldBeOn: ${shouldBeOn} Suspended: ${this.suspended} Running: ${this.running}`);
130
+ // COND 1: The schedule should be on and the schedule is not yet on.
131
+ if (shouldBeOn && !this.running && !this.suspended) {
132
+ // If the circuit is on then we need to clear the suspended flag and set the running flag.
133
+ if (cstate.isOn) {
134
+ // If the suspended flag was previously on then we need to clear it
135
+ // because the user turned it back on.
136
+ this.suspended = false;
137
+ }
138
+ ctx.setCircuit(circuit.id, true);
139
+ // Alright we are turning on the circuit. If these are body circuits then we need to determine
140
+ // whether we will be setting the setpoints/heatmode on the body.
141
+ let body = sys.bodies.find(elem => elem.circuit === circuit.id);
142
+ if (typeof body !== 'undefined') {
143
+ let heatSource = sys.board.valueMaps.heatSources.transform(this.schedule.heatSource);
144
+ if (heatSource.name !== 'nochange') {
145
+ switch (heatSource.name) {
146
+ case 'nochange':
147
+ case 'dontchange':
148
+ break;
149
+ case 'off':
150
+ ctx.setHeatMode(body.id, 'off');
151
+ break;
152
+ default:
153
+ ctx.setHeatMode(body.id, heatSource.name, this.schedule.heatSetpoint, heatSource.hasCoolSetpoint ? this.schedule.coolSetpoint : undefined);
154
+ break;
155
+ }
156
+ }
157
+ }
158
+ ssched.isOn = true;
159
+ this.running = true;
160
+ }
161
+ else if (shouldBeOn && this.running) {
162
+ // We do nothing here.
163
+ this.suspended = !cstate.isOn;
164
+ }
165
+ // Our schedule has expired it is time to turn it off.
166
+ else if (!shouldBeOn) {
167
+ // Turn this sucker off. But wait if there is an overlapping schedule then we should
168
+ // not turn it off. We will need some logic to deal with this.
169
+ if (this.running) ctx.setCircuit(circuit.id, false);
170
+ ssched.isOn = false;
171
+ this.running = false;
172
+ this.suspended = false;
173
+ }
174
+ if (!shouldBeOn && ssched.isOn === true) {
175
+ // Turn off the circuit.
176
+ ctx.setCircuit(circuit.id, false);
177
+ ssched.isOn = false;
178
+ }
179
+ ssched.emitEquipmentChange();
180
+ } catch (err) { logger.error(`Error processing schedule: ${err.message}`); }
181
+
182
+ }
183
+ protected calcTime(dt: Timestamp, type: number, offset: number): Timestamp {
184
+ let tt = sys.board.valueMaps.scheduleTimeTypes.transform(type);
185
+ switch (tt.name) {
186
+ case 'sunrise':
187
+ return new Timestamp(state.heliotrope.sunrise);
188
+ case 'sunset':
189
+ return new Timestamp(state.heliotrope.sunset);
190
+ default:
191
+ return dt.startOfDay().addMinutes(offset);
192
+ }
193
+ }
194
+ protected shouldBeOn(sstate: ScheduleState): boolean {
195
+ if (this.schedule.isActive === false) return false;
196
+ // Be careful with toDate since this returns a mutable date object from the state timestamp. startOfDay makes it immutable.
197
+ let sod = state.time.startOfDay()
198
+ let dow = sod.toDate().getDay();
199
+ let type = sys.board.valueMaps.scheduleTypes.transform(this.schedule.scheduleType);
200
+ if (type.name === 'runonce') {
201
+ // If we are not matching up with the day then we shouldn't be running.
202
+ if (sod.fullYear !== this.schedule.startYear || sod.month + 1 !== this.schedule.startMonth || sod.date !== this.schedule.startDay) return false;
203
+ }
204
+ else {
205
+ // Convert the dow to the bit value.
206
+ let sd = sys.board.valueMaps.scheduleDays.toArray().find(elem => elem.dow === dow);
207
+ let dayVal = sd.bitVal || sd.val; // The bitval allows mask overrides.
208
+ // First check to see if today is one of our days.
209
+ if ((this.schedule.scheduleDays & dayVal) === 0) return false;
210
+ }
211
+ // Next normalize our start and end times. Fortunately, the start and end times are normalized here so that
212
+ // [0, {name: 'manual', desc: 'Manual }]
213
+ // [1, { name: 'sunrise', desc: 'Sunrise' }],
214
+ // [2, { name: 'sunset', desc: 'Sunset' }]
215
+ let tmStart = this.calcTime(sod, this.schedule.startTime, this.schedule.startTime).getTime();
216
+ let tmEnd = this.calcTime(sod, this.schedule.endTimeType, this.schedule.endTime).getTime();
217
+
218
+ if (isNaN(tmStart)) return false;
219
+ if (isNaN(tmEnd)) return false;
220
+ // If we are past our window we should be off.
221
+ let tm = state.time.getTime();
222
+ if (tm >= tmEnd) return false;
223
+ if (tm <= tmStart) return false;
224
+
225
+ // If we make it here we should be on.
226
+ return true;
227
+ }
228
+ public async closeAsync() {
229
+ try {
230
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
231
+ this._pollTimer = null;
232
+ }
233
+ catch (err) { logger.error(`Nixie Schedule closeAsync: ${err.message}`); return Promise.reject(err); }
234
+ }
235
+ public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
236
+ }
237
+ class NixieScheduleContext {
238
+ constructor() {
239
+
240
+ }
241
+ public circuits: { id: number, isOn: boolean }[] = [];
242
+ public heatModes: { id: number, heatMode: number, heatSetpoint?: number, coolSetpoint?: number }[] = [];
243
+ public setCircuit(id: number, isOn: boolean) {
244
+ let c = this.circuits.find(elem => elem.id === id);
245
+ if (typeof c === 'undefined') this.circuits.push({ id: id, isOn: isOn });
246
+ else c.isOn = isOn;
247
+ }
248
+ public setHeatMode(id: number, heatMode: string, heatSetpoint?: number, coolSetpoint?: number) {
249
+ let mode = sys.board.valueMaps.heatModes.transformByName(heatMode);
250
+ let hm = this.heatModes.find(elem => elem.id == id);
251
+ if (typeof hm === 'undefined') this.heatModes.push({ id: id, heatMode: mode.val, heatSetpoint: heatSetpoint, coolSetpoint: coolSetpoint });
252
+ else {
253
+ hm.heatMode = mode.val;
254
+ if (typeof heatSetpoint !== 'undefined') hm.heatSetpoint = heatSetpoint;
255
+ if (typeof coolSetpoint !== 'undefined') hm.coolSetpoint = coolSetpoint;
256
+ }
257
+ }
258
258
  }