nodejs-poolcontroller 7.6.1 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/.eslintrc.json +36 -45
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -0
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -0
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -0
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  6. package/CONTRIBUTING.md +74 -74
  7. package/Changelog +242 -215
  8. package/Dockerfile +17 -17
  9. package/Gruntfile.js +40 -40
  10. package/LICENSE +661 -661
  11. package/README.md +195 -191
  12. package/anslq25/MessagesMock.ts +218 -0
  13. package/anslq25/boards/MockBoardFactory.ts +50 -0
  14. package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
  15. package/anslq25/boards/MockSystemBoard.ts +217 -0
  16. package/anslq25/chemistry/MockChlorinator.ts +75 -0
  17. package/anslq25/pumps/MockPump.ts +84 -0
  18. package/app.ts +10 -14
  19. package/config/Config.ts +26 -8
  20. package/config/VersionCheck.ts +8 -4
  21. package/controller/Constants.ts +59 -25
  22. package/controller/Equipment.ts +2667 -2459
  23. package/controller/Errors.ts +181 -180
  24. package/controller/Lockouts.ts +534 -436
  25. package/controller/State.ts +596 -77
  26. package/controller/boards/AquaLinkBoard.ts +1003 -0
  27. package/controller/boards/BoardFactory.ts +53 -45
  28. package/controller/boards/EasyTouchBoard.ts +3079 -2653
  29. package/controller/boards/IntelliCenterBoard.ts +3821 -4230
  30. package/controller/boards/IntelliComBoard.ts +69 -63
  31. package/controller/boards/IntelliTouchBoard.ts +384 -241
  32. package/controller/boards/NixieBoard.ts +1871 -1675
  33. package/controller/boards/SunTouchBoard.ts +393 -0
  34. package/controller/boards/SystemBoard.ts +5244 -4697
  35. package/controller/comms/Comms.ts +905 -541
  36. package/controller/comms/ScreenLogic.ts +1663 -0
  37. package/controller/comms/messages/Messages.ts +382 -54
  38. package/controller/comms/messages/config/ChlorinatorMessage.ts +8 -4
  39. package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
  40. package/controller/comms/messages/config/CircuitMessage.ts +82 -13
  41. package/controller/comms/messages/config/ConfigMessage.ts +3 -1
  42. package/controller/comms/messages/config/CoverMessage.ts +2 -1
  43. package/controller/comms/messages/config/CustomNameMessage.ts +31 -30
  44. package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
  45. package/controller/comms/messages/config/ExternalMessage.ts +33 -3
  46. package/controller/comms/messages/config/FeatureMessage.ts +2 -1
  47. package/controller/comms/messages/config/GeneralMessage.ts +2 -1
  48. package/controller/comms/messages/config/HeaterMessage.ts +145 -11
  49. package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
  50. package/controller/comms/messages/config/OptionsMessage.ts +16 -27
  51. package/controller/comms/messages/config/PumpMessage.ts +62 -47
  52. package/controller/comms/messages/config/RemoteMessage.ts +80 -13
  53. package/controller/comms/messages/config/ScheduleMessage.ts +390 -347
  54. package/controller/comms/messages/config/SecurityMessage.ts +2 -1
  55. package/controller/comms/messages/config/ValveMessage.ts +44 -27
  56. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +44 -91
  57. package/controller/comms/messages/status/EquipmentStateMessage.ts +139 -30
  58. package/controller/comms/messages/status/HeaterStateMessage.ts +135 -86
  59. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -445
  60. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -35
  61. package/controller/comms/messages/status/PumpStateMessage.ts +92 -2
  62. package/controller/comms/messages/status/VersionMessage.ts +2 -1
  63. package/controller/nixie/Nixie.ts +173 -162
  64. package/controller/nixie/NixieEquipment.ts +104 -103
  65. package/controller/nixie/bodies/Body.ts +120 -120
  66. package/controller/nixie/bodies/Filter.ts +135 -135
  67. package/controller/nixie/chemistry/ChemController.ts +2682 -2498
  68. package/controller/nixie/chemistry/ChemDoser.ts +806 -0
  69. package/controller/nixie/chemistry/Chlorinator.ts +367 -314
  70. package/controller/nixie/circuits/Circuit.ts +402 -248
  71. package/controller/nixie/heaters/Heater.ts +815 -649
  72. package/controller/nixie/pumps/Pump.ts +934 -661
  73. package/controller/nixie/schedules/Schedule.ts +319 -257
  74. package/controller/nixie/valves/Valve.ts +170 -170
  75. package/defaultConfig.json +346 -286
  76. package/logger/DataLogger.ts +448 -448
  77. package/logger/Logger.ts +38 -9
  78. package/package.json +60 -56
  79. package/tsconfig.json +25 -25
  80. package/web/Server.ts +275 -117
  81. package/web/bindings/aqualinkD.json +560 -0
  82. package/web/bindings/homeassistant.json +437 -0
  83. package/web/bindings/influxDB.json +1066 -1021
  84. package/web/bindings/mqtt.json +721 -654
  85. package/web/bindings/mqttAlt.json +746 -684
  86. package/web/bindings/rulesManager.json +54 -54
  87. package/web/bindings/smartThings-Hubitat.json +31 -31
  88. package/web/bindings/valveRelays.json +20 -20
  89. package/web/bindings/vera.json +25 -25
  90. package/web/interfaces/baseInterface.ts +188 -136
  91. package/web/interfaces/httpInterface.ts +148 -124
  92. package/web/interfaces/influxInterface.ts +283 -245
  93. package/web/interfaces/mqttInterface.ts +695 -475
  94. package/web/interfaces/ruleInterface.ts +87 -0
  95. package/web/services/config/Config.ts +177 -49
  96. package/web/services/config/ConfigSocket.ts +2 -1
  97. package/web/services/state/State.ts +154 -3
  98. package/web/services/state/StateSocket.ts +69 -18
  99. package/web/services/utilities/Utilities.ts +232 -42
  100. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -52
  101. package/config copy.json +0 -300
  102. package/issue_template.md +0 -52
@@ -1,258 +1,320 @@
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 { CircuitGroup, CircuitGroupCircuit, ICircuitGroup, ICircuitGroupCircuit, LightGroup, LightGroupCircuit, Schedule, ScheduleCollection, sys } from "../../../controller/Equipment";
7
+ import { CircuitGroupState, ICircuitGroupState, ScheduleState, state, } from "../../State";
8
+ import { setTimeout, clearTimeout } from 'timers';
9
+ import { NixieControlPanel } from '../Nixie';
10
+ import { webApp, InterfaceServerResponse } from "../../../web/Server";
11
+ import { delayMgr } from '../../../controller/Lockouts';
12
+
13
+
14
+ export class NixieScheduleCollection extends NixieEquipmentCollection<NixieSchedule> {
15
+ public async setScheduleAsync(schedule: Schedule, data: any) {
16
+ // By the time we get here we know that we are in control and this is a schedule we should be in control of.
17
+ try {
18
+ let c: NixieSchedule = this.find(elem => elem.id === schedule.id) as NixieSchedule;
19
+ if (typeof c === 'undefined') {
20
+ schedule.master = 1;
21
+ c = new NixieSchedule(this.controlPanel, schedule);
22
+ this.push(c);
23
+ await c.setScheduleAsync(data);
24
+ logger.info(`A Schedule was not found for id #${schedule.id} creating Schedule`);
25
+ }
26
+ else {
27
+ await c.setScheduleAsync(data);
28
+ }
29
+ }
30
+ catch (err) { logger.error(`setScheduleAsync: ${err.message}`); return Promise.reject(err); }
31
+ }
32
+ public async initAsync(schedules: ScheduleCollection) {
33
+ try {
34
+ for (let i = 0; i < schedules.length; i++) {
35
+ let schedule = schedules.getItemByIndex(i);
36
+ if (schedule.master === 1) {
37
+ if (typeof this.find(elem => elem.id === schedule.id) === 'undefined') {
38
+ logger.info(`Initializing Schedule ${schedule.id}`);
39
+ let nSchedule = new NixieSchedule(this.controlPanel, schedule);
40
+ this.push(nSchedule);
41
+ }
42
+ }
43
+ }
44
+ }
45
+ catch (err) { logger.error(`Nixie Schedule initAsync: ${err.message}`); return Promise.reject(err); }
46
+ }
47
+ public async triggerSchedules() {
48
+ try {
49
+ let ctx = new NixieScheduleContext();
50
+ for (let i = 0; i < this.length; i++) {
51
+ (this[i] as NixieSchedule).triggerScheduleAsync(ctx);
52
+ }
53
+ // Set the heat modes for the bodies.
54
+ for (let i = 0; i < ctx.heatModes.length; i++) {
55
+ let mode = ctx.heatModes[i];
56
+ let body = sys.bodies.getItemById(mode.id);
57
+ await sys.board.bodies.setHeatModeAsync(sys.bodies.getItemById(mode.id), mode.heatMode);
58
+ if (typeof mode.heatSetpoint !== 'undefined') await sys.board.bodies.setHeatSetpointAsync(body, mode.heatSetpoint);
59
+ if (typeof mode.coolSetpoint !== 'undefined') await sys.board.bodies.setCoolSetpointAsync(body, mode.coolSetpoint);
60
+ }
61
+ // Alright now that we are done with that we need to set all the circuit states that need changing.
62
+ for (let i = 0; i < ctx.circuits.length; i++) {
63
+ let circuit = ctx.circuits[i];
64
+ await sys.board.circuits.setCircuitStateAsync(circuit.id, circuit.isOn);
65
+ }
66
+ } catch (err) { logger.error(`Error triggering schedules: ${err}`); }
67
+ }
68
+ }
69
+ export class NixieSchedule extends NixieEquipment {
70
+ public pollingInterval: number = 10000;
71
+ private _pollTimer: NodeJS.Timeout = null;
72
+ public schedule: Schedule;
73
+ private suspended: boolean = false;
74
+ private resumed: boolean = false;
75
+ private running: boolean = false;
76
+ constructor(ncp: INixieControlPanel, schedule: Schedule) {
77
+ super(ncp);
78
+ this.schedule = schedule;
79
+ this.pollEquipmentAsync();
80
+ }
81
+ public get id(): number { return typeof this.schedule !== 'undefined' ? this.schedule.id : -1; }
82
+ public async setScheduleAsync(data: any) {
83
+ try {
84
+ let schedule = this.schedule;
85
+ }
86
+ catch (err) { logger.error(`Nixie setScheduleAsync: ${err.message}`); return Promise.reject(err); }
87
+ }
88
+ public async pollEquipmentAsync() {
89
+ let self = this;
90
+ try {
91
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
92
+ this._pollTimer = null;
93
+ let success = false;
94
+ }
95
+ catch (err) { logger.error(`Nixie Error polling Schedule - ${err}`); }
96
+ finally { this._pollTimer = setTimeout(async () => await self.pollEquipmentAsync(), this.pollingInterval || 10000); }
97
+ }
98
+ public async validateSetupAsync(Schedule: Schedule, temp: ScheduleState) {
99
+ try {
100
+ // The validation will be different if the Schedule is on or not. So lets get that information.
101
+ } catch (err) { logger.error(`Nixie Error checking Schedule Hardware ${this.schedule.id}: ${err.message}`); return Promise.reject(err); }
102
+ }
103
+ public async triggerScheduleAsync(ctx: NixieScheduleContext) {
104
+ try {
105
+ if (this.schedule.isActive === false) return;
106
+ let ssched = state.schedules.getItemById(this.id, true);
107
+ // RULES FOR NIXIE SCHEDULES
108
+ // ------------------------------------------------------
109
+ // Schedules can be overridden so it is important that when the
110
+ // state is changed for the schedule if it is currently active that
111
+ // Nixie does not override the state of the scheduled circuit or feature.
112
+ /*
113
+ RSG 5-8-22
114
+ Manual OP needs to play a role here. From the IC manual:
115
+ # Manual OP General
116
+
117
+ From the manual:
118
+ Manual OP Priority: ON: This feature allows for a circuit to be manually switched OFF and switched ON within a scheduled program, the circuit will continue to run for a maximum of 12 hours or whatever that circuit Egg Timer is set to, after which the scheduled program will resume. This feature will turn off any scheduled program to allow manual pump override. The Default setting is OFF.
119
+
120
+ ## When on
121
+ 1. If a schedule should be on and the user turns the schedule off then the schedule expires until such time as the time off has expired. When that occurs the schedule should be reset to run at the designated time. If the user resets the schedule by turning the circuit back on again ~~then the schedule will resume and turn off at the specified time~~ then the schedule will be ignored and the circuit will run until the egg timer expires or the circuit/feature is manually turned off. This setting WILL affect other schedules that may impact this circuit.
122
+
123
+ ## When off
124
+ 1. "Normal" = If a schedule should be on and the user turns the schedule off then the schedule expires until such time as the time off has expired. When that occurs the schedule should be reset to run at the designated time. If the user resets the schedule by turning the circuit back on again then the schedule will resume and turn off at the specified time.
125
+
126
+ Interestingly, there also seems to be a schedule level setting for this. We will ignore that for now as the logic could get much more complicated.
127
+ */
128
+ // 1. If the feature happens to be running and the schedule is not yet turned on then
129
+ // it should not override what the user says.
130
+ // 2. If a schedule is running and the state of the circuit changes to off then the new state should suspend the schedule
131
+ // until which time the feature is turned back on again.
132
+ // Manual OP Off: Then the off time will come into play.
133
+ // Manual OP On: The egg timer will take precedence. No other schedules will turn off this feature
134
+ // 3. Egg timers will be managed by the individual circuit. If this is being turned on via the schedule then
135
+ // the egg timer is not in effect.
136
+ // 4. If there are overlapping schedules, then the off date is determined by
137
+ // the maximum off date.
138
+ // 5. If a schedule should be on and the user turns the schedule off then the schedule expires until such time
139
+ // as the time off has expired. When that occurs the schedule should be reset to run at the designated time. If the
140
+ // user resets the schedule by turning the circuit back on again then the schedule will...
141
+ // Manual OP Off: ..resume and turn off at the specified time.
142
+ // Manual OP On: ...continue to run until the egg timer time is reached or the circuit is manually turned off.
143
+ // 6. Heat setpoints should only be changed when the schedule is first turning on the scheduled circuit.
144
+ // 7. If schedule is disabled, skip it
145
+ // 8. Manual OP On: If another schedule has been resumed and this schedule would affect that feature, do not start this schedule.
146
+
147
+ // Definitions:
148
+ // this.resumed = Flag to show if this schedule has been suspended and resumed.
149
+ // this.manualPriorityActive = Flag to show if this schedule has been suspended and resumed, and Manual Priority (global is active)
150
+ // this.delayed = Flag to show if this schedule is running, but another schedule is mOP and overriding this one.
151
+ let cstate = state.circuits.getInterfaceById(this.schedule.circuit, false);
152
+ let circuit = sys.circuits.getInterfaceById(this.schedule.circuit, false, { isActive: false });
153
+ if (circuit.isActive === false) {
154
+ ssched.isOn = false;
155
+ return;
156
+ }
157
+ let shouldBeOn = this.shouldBeOn(); // This should also set the validity for the schedule if there are errors.
158
+
159
+ let manualPriorityActive: boolean = shouldBeOn ? sys.board.schedules.manualPriorityActive(ssched) : false;
160
+ //console.log(`Processing schedule ${this.schedule.id} - ${circuit.name} : ShouldBeOn: ${shouldBeOn} ManualPriorityActive: ${manualPriorityActive} Running: ${this.running} Suspended: ${this.suspended} Resumed: ${this.resumed}`);
161
+
162
+
163
+ // COND 1: The schedule should be on and the schedule is not yet on.
164
+ if (shouldBeOn && !this.running && !this.suspended || manualPriorityActive) {
165
+ // If the circuit is on then we need to clear the suspended flag and set the running flag.
166
+ if (cstate.isOn) {
167
+ // If the suspended flag was previously on then we need to clear it
168
+ // because the user turned it back on.
169
+ this.suspended = false;
170
+ }
171
+ if (manualPriorityActive) {
172
+ ssched.manualPriorityActive = true;
173
+ ssched.isOn = false;
174
+ }
175
+ else {
176
+ ctx.setCircuit(circuit.id, true);
177
+ // Alright we are turning on the circuit. If these are body circuits then we need to determine
178
+ // whether we will be setting the setpoints/heatmode on the body.
179
+ let body = sys.bodies.find(elem => elem.circuit === circuit.id);
180
+ if (typeof body !== 'undefined') {
181
+ let heatSource = sys.board.valueMaps.heatSources.transform(this.schedule.heatSource);
182
+ if (heatSource.name !== 'nochange') {
183
+ switch (heatSource.name) {
184
+ case 'nochange':
185
+ case 'dontchange':
186
+ break;
187
+ case 'off':
188
+ ctx.setHeatMode(body.id, 'off');
189
+ break;
190
+ default:
191
+ ctx.setHeatMode(body.id, heatSource.name, this.schedule.heatSetpoint, heatSource.hasCoolSetpoint ? this.schedule.coolSetpoint : undefined);
192
+ break;
193
+ }
194
+ }
195
+ }
196
+ ssched.manualPriorityActive = false;
197
+ ssched.isOn = true;
198
+ }
199
+ this.running = true;
200
+ }
201
+ else if (shouldBeOn && this.running) {
202
+ // With mOP, we need to see if the schedule will come back into play and also set the circut
203
+ if (this.suspended && cstate.isOn) {
204
+ if (sys.general.options.manualPriority) {
205
+ delayMgr.setManualPriorityDelay(cstate);
206
+ ssched.manualPriorityActive = true;
207
+ }
208
+ this.resumed = true;
209
+ }
210
+ this.suspended = !cstate.isOn;
211
+ if (manualPriorityActive){
212
+ ssched.isOn = false;
213
+ ssched.manualPriorityActive = true;
214
+ }
215
+ else {
216
+ ssched.isOn = cstate.isOn;
217
+ ssched.manualPriorityActive = false;
218
+ }
219
+ }
220
+ // Our schedule has expired it is time to turn it off, but only if !manualPriorityActive.
221
+ else if (!shouldBeOn) {
222
+ // Turn this sucker off. But wait if there is an overlapping schedule then we should
223
+ // not turn it off. We will need some logic to deal with this.
224
+ if (this.running && !cstate.manualPriorityActive) ctx.setCircuit(circuit.id, false);
225
+ ssched.isOn = false;
226
+ this.running = false;
227
+ this.suspended = false;
228
+ this.resumed = false;
229
+ ssched.manualPriorityActive = false;
230
+ }
231
+ if (!shouldBeOn && ssched.isOn === true) {
232
+ // Turn off the circuit.
233
+ if (!manualPriorityActive) ctx.setCircuit(circuit.id, false);
234
+ ssched.isOn = false;
235
+ }
236
+ ssched.emitEquipmentChange();
237
+ } catch (err) { logger.error(`Error processing schedule: ${err.message}`); }
238
+
239
+ }
240
+ protected calcTime(dt: Timestamp, type: number, offset: number): Timestamp {
241
+ let tt = sys.board.valueMaps.scheduleTimeTypes.transform(type);
242
+ switch (tt.name) {
243
+ case 'sunrise':
244
+ return new Timestamp(state.heliotrope.sunrise);
245
+ case 'sunset':
246
+ return new Timestamp(state.heliotrope.sunset);
247
+ default:
248
+ return dt.startOfDay().addMinutes(offset);
249
+ }
250
+ }
251
+ protected shouldBeOn(): boolean {
252
+ if (this.schedule.isActive === false) return false;
253
+ if (this.schedule.disabled) return false;
254
+ // Be careful with toDate since this returns a mutable date object from the state timestamp. startOfDay makes it immutable.
255
+ let sod = state.time.startOfDay()
256
+ let dow = sod.toDate().getDay();
257
+ let type = sys.board.valueMaps.scheduleTypes.transform(this.schedule.scheduleType);
258
+ if (type.name === 'runonce') {
259
+ // If we are not matching up with the day then we shouldn't be running.
260
+ if (sod.fullYear !== this.schedule.startYear || sod.month + 1 !== this.schedule.startMonth || sod.date !== this.schedule.startDay) return false;
261
+ }
262
+ else {
263
+ // Convert the dow to the bit value.
264
+ let sd = sys.board.valueMaps.scheduleDays.toArray().find(elem => elem.dow === dow);
265
+ let dayVal = sd.bitVal || sd.val; // The bitval allows mask overrides.
266
+ // First check to see if today is one of our days.
267
+ if ((this.schedule.scheduleDays & dayVal) === 0) return false;
268
+ }
269
+ // Next normalize our start and end times. Fortunately, the start and end times are normalized here so that
270
+ // [0, {name: 'manual', desc: 'Manual }]
271
+ // [1, { name: 'sunrise', desc: 'Sunrise' }],
272
+ // [2, { name: 'sunset', desc: 'Sunset' }]
273
+ let tmStart = this.calcTime(sod, this.schedule.startTimeType, this.schedule.startTime).getTime();
274
+ let tmEnd = this.calcTime(sod, this.schedule.endTimeType, this.schedule.endTime).getTime();
275
+
276
+ if (isNaN(tmStart)) return false;
277
+ if (isNaN(tmEnd)) return false;
278
+ // If we are past our window we should be off.
279
+ let tm = state.time.getTime();
280
+ if (tm >= tmEnd) return false;
281
+ if (tm <= tmStart) return false;
282
+
283
+ // Let's now check to see
284
+
285
+ // If we make it here we should be on.
286
+ return true;
287
+ }
288
+
289
+
290
+ public async closeAsync() {
291
+ try {
292
+ if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
293
+ this._pollTimer = null;
294
+ }
295
+ catch (err) { logger.error(`Nixie Schedule closeAsync: ${err.message}`); return Promise.reject(err); }
296
+ }
297
+ public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
298
+ }
299
+ class NixieScheduleContext {
300
+ constructor() {
301
+
302
+ }
303
+ public circuits: { id: number, isOn: boolean }[] = [];
304
+ public heatModes: { id: number, heatMode: number, heatSetpoint?: number, coolSetpoint?: number }[] = [];
305
+ public setCircuit(id: number, isOn: boolean) {
306
+ let c = this.circuits.find(elem => elem.id === id);
307
+ if (typeof c === 'undefined') this.circuits.push({ id: id, isOn: isOn });
308
+ else c.isOn = isOn;
309
+ }
310
+ public setHeatMode(id: number, heatMode: string, heatSetpoint?: number, coolSetpoint?: number) {
311
+ let mode = sys.board.valueMaps.heatModes.transformByName(heatMode);
312
+ let hm = this.heatModes.find(elem => elem.id == id);
313
+ if (typeof hm === 'undefined') this.heatModes.push({ id: id, heatMode: mode.val, heatSetpoint: heatSetpoint, coolSetpoint: coolSetpoint });
314
+ else {
315
+ hm.heatMode = mode.val;
316
+ if (typeof heatSetpoint !== 'undefined') hm.heatSetpoint = heatSetpoint;
317
+ if (typeof coolSetpoint !== 'undefined') hm.coolSetpoint = coolSetpoint;
318
+ }
319
+ }
258
320
  }