nodejs-poolcontroller 8.0.1 → 8.0.4
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.
- package/.github/workflows/docker-publish-njsPC-linux.yml +50 -0
- package/Dockerfile +5 -3
- package/README.md +4 -1
- package/config/Config.ts +1 -1
- package/controller/Constants.ts +164 -67
- package/controller/Equipment.ts +78 -18
- package/controller/Lockouts.ts +15 -0
- package/controller/State.ts +281 -7
- package/controller/boards/EasyTouchBoard.ts +225 -101
- package/controller/boards/IntelliCenterBoard.ts +84 -23
- package/controller/boards/IntelliTouchBoard.ts +2 -4
- package/controller/boards/NixieBoard.ts +84 -27
- package/controller/boards/SunTouchBoard.ts +8 -2
- package/controller/boards/SystemBoard.ts +3262 -3242
- package/controller/comms/ScreenLogic.ts +47 -44
- package/controller/comms/messages/Messages.ts +4 -4
- package/controller/comms/messages/config/ChlorinatorMessage.ts +10 -3
- package/controller/comms/messages/config/ExternalMessage.ts +4 -1
- package/controller/comms/messages/config/PumpMessage.ts +8 -7
- package/controller/comms/messages/config/RemoteMessage.ts +48 -43
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -2
- package/controller/comms/messages/status/EquipmentStateMessage.ts +9 -4
- package/controller/comms/messages/status/PumpStateMessage.ts +1 -1
- package/controller/nixie/NixieEquipment.ts +1 -1
- package/controller/nixie/bodies/Body.ts +1 -1
- package/controller/nixie/chemistry/ChemController.ts +37 -28
- package/controller/nixie/circuits/Circuit.ts +48 -7
- package/controller/nixie/heaters/Heater.ts +24 -5
- package/controller/nixie/pumps/Pump.ts +159 -97
- package/controller/nixie/schedules/Schedule.ts +207 -126
- package/defaultConfig.json +3 -3
- package/logger/DataLogger.ts +7 -7
- package/package.json +2 -2
- package/sendSocket.js +32 -0
- package/web/Server.ts +17 -11
- package/web/bindings/homeassistant.json +2 -2
- package/web/interfaces/mqttInterface.ts +18 -18
- package/web/services/config/Config.ts +34 -1
- package/web/services/state/State.ts +10 -3
- package/web/services/state/StateSocket.ts +7 -3
- package/web/services/utilities/Utilities.ts +3 -3
|
@@ -4,11 +4,12 @@ import { logger } from '../../../logger/Logger';
|
|
|
4
4
|
|
|
5
5
|
import { NixieEquipment, NixieChildEquipment, NixieEquipmentCollection, INixieControlPanel } from "../NixieEquipment";
|
|
6
6
|
import { CircuitGroup, CircuitGroupCircuit, ICircuitGroup, ICircuitGroupCircuit, LightGroup, LightGroupCircuit, Schedule, ScheduleCollection, sys } from "../../../controller/Equipment";
|
|
7
|
-
import { CircuitGroupState, ICircuitGroupState, ScheduleState, state, } from "../../State";
|
|
7
|
+
import { ICircuitState, CircuitGroupState, ICircuitGroupState, ScheduleState, ScheduleTime, state, } from "../../State";
|
|
8
8
|
import { setTimeout, clearTimeout } from 'timers';
|
|
9
9
|
import { NixieControlPanel } from '../Nixie';
|
|
10
10
|
import { webApp, InterfaceServerResponse } from "../../../web/Server";
|
|
11
11
|
import { delayMgr } from '../../../controller/Lockouts';
|
|
12
|
+
import { time } from 'console';
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
export class NixieScheduleCollection extends NixieEquipmentCollection<NixieSchedule> {
|
|
@@ -23,9 +24,8 @@ export class NixieScheduleCollection extends NixieEquipmentCollection<NixieSched
|
|
|
23
24
|
await c.setScheduleAsync(data);
|
|
24
25
|
logger.info(`A Schedule was not found for id #${schedule.id} creating Schedule`);
|
|
25
26
|
}
|
|
26
|
-
else
|
|
27
|
+
else
|
|
27
28
|
await c.setScheduleAsync(data);
|
|
28
|
-
}
|
|
29
29
|
}
|
|
30
30
|
catch (err) { logger.error(`setScheduleAsync: ${err.message}`); return Promise.reject(err); }
|
|
31
31
|
}
|
|
@@ -46,24 +46,168 @@ export class NixieScheduleCollection extends NixieEquipmentCollection<NixieSched
|
|
|
46
46
|
}
|
|
47
47
|
public async triggerSchedules() {
|
|
48
48
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let
|
|
57
|
-
|
|
58
|
-
if (typeof
|
|
59
|
-
|
|
49
|
+
// This is a listing of all the active schedules that are either currently on or should be on.
|
|
50
|
+
let sscheds: ScheduleState[] = state.schedules.getActiveSchedules();
|
|
51
|
+
// Go through all the schedules and hash them by circuit id.
|
|
52
|
+
let circuits: { circuitId: number, cstate: ICircuitState, hasNixie: boolean, sscheds: ScheduleState[] }[] = []
|
|
53
|
+
for (let i = 0; i < sscheds.length; i++) {
|
|
54
|
+
// We only care about schedules that are currently running or should be running.
|
|
55
|
+
if (!sscheds[i].isOn && !sscheds[i].scheduleTime.shouldBeOn) continue;
|
|
56
|
+
let circ = circuits.find(elem => elem.circuitId === sscheds[i].circuit);
|
|
57
|
+
let sched = sys.schedules.getItemById(sscheds[i].id)
|
|
58
|
+
if (typeof circ === 'undefined') circuits.push({
|
|
59
|
+
circuitId: sscheds[i].circuit,
|
|
60
|
+
cstate: state.circuits.getInterfaceById(sscheds[i].circuit), hasNixie: sched.master !== 0, sscheds: [sscheds[i]]
|
|
61
|
+
});
|
|
62
|
+
else {
|
|
63
|
+
if (sched.master !== 0) circ.hasNixie = true;
|
|
64
|
+
circ.sscheds.push(sscheds[i]);
|
|
65
|
+
}
|
|
60
66
|
}
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
// Sort this so that body circuits are evaluated first. This is required when there are schedules for things like cleaner
|
|
68
|
+
// or delay circuits. If we do not do this then a schedule that requires the pool to be on for instance will never
|
|
69
|
+
// get triggered.
|
|
70
|
+
circuits.sort((x, y) => y.circuitId === 6 || y.circuitId === 1 ? 1 : y.circuitId - x.circuitId);
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
RSG 5-8-22
|
|
75
|
+
Manual OP needs to play a role here.From the IC manual:
|
|
76
|
+
# Manual OP General
|
|
77
|
+
|
|
78
|
+
From the manual:
|
|
79
|
+
Manual OP Priority: ON: This feature allows for a circuit to be manually switched OFF and switched ON within a scheduled program,
|
|
80
|
+
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
|
|
81
|
+
program will resume. This feature will turn off any scheduled program to allow manual pump override.The Default setting is OFF.
|
|
82
|
+
|
|
83
|
+
## When on
|
|
84
|
+
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
|
|
85
|
+
expired.When that occurs the schedule should be reset to run at the designated time.If the user resets the schedule by turning the
|
|
86
|
+
circuit back on again ~~then the schedule will resume and turn off at the specified time~~then the schedule will be ignored and
|
|
87
|
+
the circuit will run until the egg timer expires or the circuit / feature is manually turned off.This setting WILL affect
|
|
88
|
+
other schedules that may impact this circuit.
|
|
89
|
+
|
|
90
|
+
## When off
|
|
91
|
+
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
|
|
92
|
+
off has expired.When that occurs the schedule should be reset to run at the designated time.If the user resets the schedule by
|
|
93
|
+
turning the circuit back on again then the schedule will resume and turn off at the specified time.
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
let mOP = sys.general.options.manualPriority;
|
|
97
|
+
|
|
98
|
+
// Now lets evaluate the schedules by virtue of their state related to the circuits.
|
|
99
|
+
for (let i = 0; i < circuits.length; i++) {
|
|
100
|
+
let c = circuits[i];
|
|
101
|
+
if (!c.hasNixie) continue; // If this has nothing to do with Nixie move on.
|
|
102
|
+
let shouldBeOn = typeof c.sscheds.find(elem => elem.scheduleTime.shouldBeOn === true) !== 'undefined';
|
|
103
|
+
// 1. If the feature is currently running and the schedule is not on then it will set the priority for the circuit to [scheduled].
|
|
104
|
+
// 2. If the feature is currently running but there are overlapping schedules then this will catch any schedules that need to be turned off.
|
|
105
|
+
if (c.cstate.isOn && shouldBeOn) {
|
|
106
|
+
c.cstate.priority = shouldBeOn ? 'scheduled' : 'manual';
|
|
107
|
+
for (let j = 0; j < c.sscheds.length; j++) {
|
|
108
|
+
let ssched = c.sscheds[j];
|
|
109
|
+
ssched.triggered = ssched.scheduleTime.shouldBeOn;
|
|
110
|
+
if (mOP && ssched.manualPriorityActive) {
|
|
111
|
+
ssched.isOn = false;
|
|
112
|
+
// Not sure what setting a delay for this does but ok.
|
|
113
|
+
if (!c.cstate.manualPriorityActive) delayMgr.setManualPriorityDelay(c.cstate);
|
|
114
|
+
}
|
|
115
|
+
else ssched.isOn = ssched.scheduleTime.shouldBeOn && !ssched.manualPriorityActive;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// 3. If the schedule should be on and it isn't and the schedule has not been triggered then we need to
|
|
119
|
+
// turn the schedule and circuit on.
|
|
120
|
+
else if (!c.cstate.isOn && shouldBeOn) {
|
|
121
|
+
// The circuit is not on but it should be. Check to ensure all schedules have been triggered.
|
|
122
|
+
let untriggered = false;
|
|
123
|
+
// If this schedule has been triggered then mOP comes into play if manualPriority has been set in the config.
|
|
124
|
+
for (let j = 0; j < c.sscheds.length; j++) {
|
|
125
|
+
let ssched = c.sscheds[j];
|
|
126
|
+
// If this schedule is turned back on then the egg timer will come into play. This is all that is required
|
|
127
|
+
// for the mOP function. The setEndDate for the circuit makes the determination as to when off will occur.
|
|
128
|
+
if (mOP && ssched.scheduleTime.shouldBeOn && ssched.triggered) {
|
|
129
|
+
ssched.manualPriorityActive = true;
|
|
130
|
+
}
|
|
131
|
+
// The reason we check to see if anything has not been triggered is so we do not have to perform the circuit changes
|
|
132
|
+
// if the schedule has already been triggered.
|
|
133
|
+
else if (!ssched.triggered) untriggered = true;
|
|
134
|
+
}
|
|
135
|
+
let heatSource = { heatMode: 'nochange', heatSetpoint: undefined, coolSetpoint: undefined };
|
|
136
|
+
// Check to see if any of the schedules have not been triggered. If they haven't then trigger them and turn the circuit on.
|
|
137
|
+
if (untriggered) {
|
|
138
|
+
// Get the heat modes and temps for all the schedules that have not been triggered.
|
|
139
|
+
let body = sys.bodies.find(elem => elem.circuit === c.circuitId);
|
|
140
|
+
if (typeof body !== 'undefined') {
|
|
141
|
+
// If this is a body circuit then we need to set the heat mode and the temperature but only do this once. If
|
|
142
|
+
// the user changes it later then that is on them.
|
|
143
|
+
for (let j = 0; j < c.sscheds.length; j++) {
|
|
144
|
+
if (sscheds[j].triggered) continue;
|
|
145
|
+
let ssched = sscheds[j];
|
|
146
|
+
let hs = sys.board.valueMaps.heatSources.transform(c.sscheds[i].heatSource);
|
|
147
|
+
switch (hs.name) {
|
|
148
|
+
case 'nochange':
|
|
149
|
+
case 'dontchange':
|
|
150
|
+
break;
|
|
151
|
+
case 'off':
|
|
152
|
+
// If the heatsource setting is off only change it if it is currently don't change.
|
|
153
|
+
if (heatSource.heatMode === 'nochange') heatSource.heatMode = hs.name;
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
switch (heatSource.heatMode) {
|
|
157
|
+
case 'off':
|
|
158
|
+
case 'nochange':
|
|
159
|
+
case 'dontchange':
|
|
160
|
+
heatSource.heatMode = hs.name;
|
|
161
|
+
heatSource.heatSetpoint = ssched.heatSetpoint;
|
|
162
|
+
heatSource.coolSetpoint = hs.hasCoolSetpoint ? ssched.coolSetpoint : undefined;
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
// Ok if we need to change the setpoint or the heatmode then lets do it.
|
|
168
|
+
if (heatSource.heatMode !== 'nochange') {
|
|
169
|
+
await sys.board.bodies.setHeatModeAsync(body, sys.board.valueMaps.heatSources.getValue(heatSource.heatMode));
|
|
170
|
+
if (typeof heatSource.heatSetpoint !== 'undefined') await sys.board.bodies.setHeatSetpointAsync(body, heatSource.heatSetpoint);
|
|
171
|
+
if (typeof heatSource.coolSetpoint !== 'undefined') await sys.board.bodies.setCoolSetpointAsync(body, heatSource.coolSetpoint);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// By now we have everything we need to turn on the circuit.
|
|
176
|
+
for (let j = 0; j < c.sscheds.length; j++) {
|
|
177
|
+
let ssched = c.sscheds[j];
|
|
178
|
+
if (!ssched.triggered && ssched.scheduleTime.shouldBeOn) {
|
|
179
|
+
if (!c.cstate.isOn) {
|
|
180
|
+
await sys.board.circuits.setCircuitStateAsync(c.circuitId, true);
|
|
181
|
+
}
|
|
182
|
+
let ssched = c.sscheds[j];
|
|
183
|
+
c.cstate.priority = 'scheduled';
|
|
184
|
+
ssched.triggered = ssched.isOn = ssched.scheduleTime.shouldBeOn;
|
|
185
|
+
ssched.manualPriorityActive = false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (c.cstate.isOn && !shouldBeOn) {
|
|
191
|
+
// Time to turn off the schedule.
|
|
192
|
+
for (let j = 0; j < c.sscheds.length; j++) {
|
|
193
|
+
let ssched = c.sscheds[j];
|
|
194
|
+
// Only turn off the schedule if it is not actively mOP.
|
|
195
|
+
if (c.cstate.isOn && !ssched.manualPriorityActive) await sys.board.circuits.setCircuitStateAsync(c.circuitId, false);
|
|
196
|
+
c.cstate.priority = 'manual';
|
|
197
|
+
// The schedule has expired we need to clear all the info for it.
|
|
198
|
+
ssched.manualPriorityActive = ssched.triggered = ssched.isOn = c.sscheds[j].scheduleTime.shouldBeOn;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
else if (!c.cstate.isOn && !shouldBeOn) {
|
|
202
|
+
// Everything is off so lets clear it all.
|
|
203
|
+
for (let j = 0; j < c.sscheds.length; j++) {
|
|
204
|
+
let ssched = c.sscheds[j];
|
|
205
|
+
ssched.isOn = ssched.manualPriorityActive = ssched.triggered = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
state.emitEquipmentChanges();
|
|
65
209
|
}
|
|
66
|
-
} catch (err) { logger.error(`Error triggering schedules: ${err}`); }
|
|
210
|
+
} catch (err) { logger.error(`Error triggering nixie schedules: ${err.message}`); }
|
|
67
211
|
}
|
|
68
212
|
}
|
|
69
213
|
export class NixieSchedule extends NixieEquipment {
|
|
@@ -77,29 +221,44 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
77
221
|
super(ncp);
|
|
78
222
|
this.schedule = schedule;
|
|
79
223
|
this.pollEquipmentAsync();
|
|
224
|
+
let ssched = state.schedules.getItemById(schedule.id, true);
|
|
225
|
+
ssched.circuit = schedule.circuit;
|
|
226
|
+
ssched.scheduleDays = schedule.scheduleDays;
|
|
227
|
+
ssched.scheduleType = schedule.scheduleType;
|
|
228
|
+
ssched.changeHeatSetpoint = schedule.changeHeatSetpoint;
|
|
229
|
+
ssched.heatSetpoint = schedule.heatSetpoint;
|
|
230
|
+
ssched.coolSetpoint = schedule.coolSetpoint;
|
|
231
|
+
ssched.heatSource = schedule.heatSource;
|
|
232
|
+
ssched.startTime = schedule.startTime;
|
|
233
|
+
ssched.endTime = schedule.endTime;
|
|
234
|
+
ssched.startTimeType = schedule.startTimeType;
|
|
235
|
+
ssched.endTimeType = schedule.endTimeType;
|
|
236
|
+
ssched.startDate = schedule.startDate;
|
|
80
237
|
}
|
|
81
238
|
public get id(): number { return typeof this.schedule !== 'undefined' ? this.schedule.id : -1; }
|
|
82
239
|
public async setScheduleAsync(data: any) {
|
|
83
240
|
try {
|
|
84
241
|
let schedule = this.schedule;
|
|
242
|
+
let sschedule = state.schedules.getItemById(schedule.id);
|
|
243
|
+
sschedule.scheduleTime.calculated = false;
|
|
85
244
|
}
|
|
86
245
|
catch (err) { logger.error(`Nixie setScheduleAsync: ${err.message}`); return Promise.reject(err); }
|
|
87
246
|
}
|
|
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
|
-
}
|
|
247
|
+
public async pollEquipmentAsync() {}
|
|
98
248
|
public async validateSetupAsync(Schedule: Schedule, temp: ScheduleState) {
|
|
99
249
|
try {
|
|
100
250
|
// The validation will be different if the Schedule is on or not. So lets get that information.
|
|
101
251
|
} catch (err) { logger.error(`Nixie Error checking Schedule Hardware ${this.schedule.id}: ${err.message}`); return Promise.reject(err); }
|
|
102
252
|
}
|
|
253
|
+
public async closeAsync() {
|
|
254
|
+
try {
|
|
255
|
+
if (typeof this._pollTimer !== 'undefined' || this._pollTimer) clearTimeout(this._pollTimer);
|
|
256
|
+
this._pollTimer = null;
|
|
257
|
+
}
|
|
258
|
+
catch (err) { logger.error(`Nixie Schedule closeAsync: ${err.message}`); return Promise.reject(err); }
|
|
259
|
+
}
|
|
260
|
+
public logData(filename: string, data: any) { this.controlPanel.logData(filename, data); }
|
|
261
|
+
/*
|
|
103
262
|
public async triggerScheduleAsync(ctx: NixieScheduleContext) {
|
|
104
263
|
try {
|
|
105
264
|
if (this.schedule.isActive === false) return;
|
|
@@ -109,22 +268,20 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
109
268
|
// Schedules can be overridden so it is important that when the
|
|
110
269
|
// state is changed for the schedule if it is currently active that
|
|
111
270
|
// Nixie does not override the state of the scheduled circuit or feature.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
Manual OP
|
|
115
|
-
# Manual OP General
|
|
271
|
+
//RSG 5-8-22
|
|
272
|
+
//Manual OP needs to play a role here. From the IC manual:
|
|
273
|
+
//# Manual OP General
|
|
116
274
|
|
|
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.
|
|
275
|
+
//From the manual:
|
|
276
|
+
//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
277
|
|
|
120
|
-
|
|
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.
|
|
278
|
+
//## When on
|
|
279
|
+
//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
280
|
|
|
123
|
-
|
|
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.
|
|
281
|
+
//## When off
|
|
282
|
+
//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
283
|
|
|
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
|
-
*/
|
|
284
|
+
//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.
|
|
128
285
|
// 1. If the feature happens to be running and the schedule is not yet turned on then
|
|
129
286
|
// it should not override what the user says.
|
|
130
287
|
// 2. If a schedule is running and the state of the circuit changes to off then the new state should suspend the schedule
|
|
@@ -154,8 +311,7 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
154
311
|
ssched.isOn = false;
|
|
155
312
|
return;
|
|
156
313
|
}
|
|
157
|
-
let shouldBeOn =
|
|
158
|
-
|
|
314
|
+
let shouldBeOn = ssched.shouldBeOn(); // This should also set the validity for the schedule if there are errors.
|
|
159
315
|
let manualPriorityActive: boolean = shouldBeOn ? sys.board.schedules.manualPriorityActive(ssched) : false;
|
|
160
316
|
//console.log(`Processing schedule ${this.schedule.id} - ${circuit.name} : ShouldBeOn: ${shouldBeOn} ManualPriorityActive: ${manualPriorityActive} Running: ${this.running} Suspended: ${this.suspended} Resumed: ${this.resumed}`);
|
|
161
317
|
|
|
@@ -199,6 +355,11 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
199
355
|
this.running = true;
|
|
200
356
|
}
|
|
201
357
|
else if (shouldBeOn && this.running) {
|
|
358
|
+
// Check to see if circuit is on, if not turn it on.
|
|
359
|
+
// RKS: 07-09-23 - This was in PR#819 buut this needs further review since the circuit states are not to be set here. This would
|
|
360
|
+
// trash delays and manualPriority.
|
|
361
|
+
// if(!cstate.isOn) ctx.setCircuit(circuit.id, true);
|
|
362
|
+
|
|
202
363
|
// With mOP, we need to see if the schedule will come back into play and also set the circut
|
|
203
364
|
if (this.suspended && cstate.isOn) {
|
|
204
365
|
if (sys.general.options.manualPriority) {
|
|
@@ -208,13 +369,13 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
208
369
|
this.resumed = true;
|
|
209
370
|
}
|
|
210
371
|
this.suspended = !cstate.isOn;
|
|
211
|
-
if (manualPriorityActive){
|
|
372
|
+
if (manualPriorityActive) {
|
|
212
373
|
ssched.isOn = false;
|
|
213
374
|
ssched.manualPriorityActive = true;
|
|
214
375
|
}
|
|
215
376
|
else {
|
|
216
377
|
ssched.isOn = cstate.isOn;
|
|
217
|
-
ssched.manualPriorityActive = false;
|
|
378
|
+
ssched.manualPriorityActive = false;
|
|
218
379
|
}
|
|
219
380
|
}
|
|
220
381
|
// Our schedule has expired it is time to turn it off, but only if !manualPriorityActive.
|
|
@@ -235,86 +396,6 @@ export class NixieSchedule extends NixieEquipment {
|
|
|
235
396
|
}
|
|
236
397
|
ssched.emitEquipmentChange();
|
|
237
398
|
} catch (err) { logger.error(`Error processing schedule: ${err.message}`); }
|
|
238
|
-
|
|
239
399
|
}
|
|
240
|
-
|
|
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); }
|
|
400
|
+
*/
|
|
298
401
|
}
|
|
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
|
-
}
|
|
320
|
-
}
|
package/defaultConfig.json
CHANGED
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
"username": "",
|
|
165
165
|
"password": "",
|
|
166
166
|
"selfSignedCertificate": false,
|
|
167
|
-
"rootTopic": "@bind=(state.equipment.model).replace(
|
|
167
|
+
"rootTopic": "@bind=(state.equipment.model).replace(/ /g,'-').replace('/','').toLowerCase();",
|
|
168
168
|
"retain": true,
|
|
169
169
|
"qos": 0,
|
|
170
170
|
"changesOnly": true
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
"port": 1883,
|
|
205
205
|
"username": "",
|
|
206
206
|
"password": "",
|
|
207
|
-
"rootTopic": "@bind=(state.equipment.model).replace(
|
|
207
|
+
"rootTopic": "@bind=(state.equipment.model).replace(/ /,'-').replace('/','').toLowerCase();Alt",
|
|
208
208
|
"retain": true,
|
|
209
209
|
"qos": 0,
|
|
210
210
|
"changesOnly": true
|
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
"vars": {
|
|
231
231
|
"_note": "hassTopic is the topic that HASS reads for configuration and should not be changed. mqttTopic should match the topic in the MQTT binding (do not use MQTTAlt for HASS).",
|
|
232
232
|
"hassTopic": "homeassistant",
|
|
233
|
-
"mqttTopic": "@bind=(state.equipment.model).replace(
|
|
233
|
+
"mqttTopic": "@bind=(state.equipment.model).replace(/ /g,'-').replace('/','').toLowerCase();"
|
|
234
234
|
}
|
|
235
235
|
},
|
|
236
236
|
"rem": {
|
package/logger/DataLogger.ts
CHANGED
|
@@ -35,7 +35,7 @@ export class DataLogger {
|
|
|
35
35
|
} catch (err) { logger.error(`Skipping invalid dose history entry: ${err.message}`); }
|
|
36
36
|
}
|
|
37
37
|
return arr;
|
|
38
|
-
} catch (err) { logger.error(err); }
|
|
38
|
+
} catch (err) { logger.error(`Skipping dose history ${err.message}`); }
|
|
39
39
|
}
|
|
40
40
|
// This method uses a callback to end the file read from the end of the file. If the callback returns false then the iteration through the
|
|
41
41
|
// file will end and the log entries will be returned. If the callback returns true then the entry will be added to the
|
|
@@ -103,11 +103,11 @@ export class DataLogger {
|
|
|
103
103
|
}
|
|
104
104
|
catch (err) { return Promise.reject(err); }
|
|
105
105
|
finally { if (typeof file !== 'undefined') await new Promise<boolean>((resolve, reject) => fs.close(file, (err) => { if (err) reject(err); else resolve(true); })); }
|
|
106
|
-
} catch (err) { logger.error(err); }
|
|
106
|
+
} catch (err) { logger.error(`readFromEndAsync: ${err.message}`); }
|
|
107
107
|
}
|
|
108
108
|
return arr;
|
|
109
109
|
}
|
|
110
|
-
catch (err) { logger.error(err); }
|
|
110
|
+
catch (err) { logger.error(`readFromEndAsync: ${logFile} ${err.message}`); }
|
|
111
111
|
|
|
112
112
|
}
|
|
113
113
|
// This method uses a callback to end the file read from the end of the file. If the callback returns false then the iteration through the
|
|
@@ -235,11 +235,11 @@ export class DataLogger {
|
|
|
235
235
|
}
|
|
236
236
|
catch (err) { return Promise.reject(err); }
|
|
237
237
|
finally { if (typeof file !== 'undefined') await new Promise<boolean>((resolve, reject) => fs.close(file, (err) => { if (err) reject(err); else resolve(true); })); }
|
|
238
|
-
} catch (err) { logger.error(err); }
|
|
238
|
+
} catch (err) { logger.error(`readFromStart: ${err.message}`); }
|
|
239
239
|
}
|
|
240
240
|
return arr;
|
|
241
241
|
}
|
|
242
|
-
catch (err) { logger.error(err); }
|
|
242
|
+
catch (err) { logger.error(`readFromStart ${logFile}: ${err.message}`); }
|
|
243
243
|
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -316,7 +316,7 @@ export class DataLogger {
|
|
|
316
316
|
else
|
|
317
317
|
lines.unshift(data.toString());
|
|
318
318
|
fs.writeFileSync(logPath, lines.join('\n'));
|
|
319
|
-
} catch (err) { logger.error(err); }
|
|
319
|
+
} catch (err) { logger.error(`writeStart ${logFile}: ${err.message}`); }
|
|
320
320
|
}
|
|
321
321
|
public static writeEnd(logFile: string, entry: DataLoggerEntry) {
|
|
322
322
|
try {
|
|
@@ -384,7 +384,7 @@ export class DataLogger {
|
|
|
384
384
|
finally { if (typeof file !== 'undefined') await new Promise<boolean>((resolve, reject) => fs.close(file, (err) => { if (err) reject(err); else resolve(true); })); }
|
|
385
385
|
}
|
|
386
386
|
return lines;
|
|
387
|
-
} catch (err) { logger.error(err); }
|
|
387
|
+
} catch (err) { logger.error(`readEnd ${logFile}: ${err.message}`); }
|
|
388
388
|
}
|
|
389
389
|
private static makeLogFilePath(logFile: string) { return `${DataLogger.ensureLogPath()}/${logFile}`; }
|
|
390
390
|
private static ensureLogPath(): string {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodejs-poolcontroller",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.4",
|
|
4
4
|
"description": "nodejs-poolController",
|
|
5
5
|
"main": "app.js",
|
|
6
6
|
"author": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"multicast-dns": "^7.2.4",
|
|
34
34
|
"node-screenlogic": "^2.0.0",
|
|
35
35
|
"node-ssdp": "^4.0.1",
|
|
36
|
-
"serialport": "^
|
|
36
|
+
"serialport": "^11.0.0",
|
|
37
37
|
"socket.io": "^4.5.0",
|
|
38
38
|
"socket.io-client": "^4.5.0",
|
|
39
39
|
"source-map-support": "^0.5.21",
|
package/sendSocket.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Import Socket.IO client
|
|
2
|
+
const io = require('socket.io-client');
|
|
3
|
+
|
|
4
|
+
// Connect to the server
|
|
5
|
+
const socket = io('http://localhost:4200');
|
|
6
|
+
|
|
7
|
+
// Event handler for successful connection
|
|
8
|
+
socket.on('connect', () => {
|
|
9
|
+
console.log('Connected to server.');
|
|
10
|
+
|
|
11
|
+
// Emit data to the server
|
|
12
|
+
socket.emit('message', 'Hello, server!');
|
|
13
|
+
socket.emit('echo', `testing 123`);
|
|
14
|
+
socket.on('echo', (string)=>{
|
|
15
|
+
console.log(string);
|
|
16
|
+
})
|
|
17
|
+
//const hexData = "02 10 01 01 14 00 03 10 02 10 01 01 14 00 03 10 02 10 01 01 14 00 03 10 02 10 02 01 80 20 00 00 00 00 00 00 b5 00 03 10 02 10 03 01 20 20 6f 50 6c 6f 54 20 6d 65 20 70 37 20 5f 34 20 46 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 07 00 10 d6 10 03 01 02 00 01 10 14 10 03 01 02 00 01 10 14 10 03 01 02 00 01 10 14";
|
|
18
|
+
const hexData = "10 02 01 80 20 00 00 00 00 00 00 b5 00 03 10 02 10 03";
|
|
19
|
+
const formattedHexData = hexData.split(' ').map(hex => parseInt(hex, 16));
|
|
20
|
+
//socket.emit('rawbytes', Buffer.from([1, 2, 3]));
|
|
21
|
+
socket.emit('rawbytes', formattedHexData);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Event handler for receiving data from the server
|
|
25
|
+
socket.on('message', (data) => {
|
|
26
|
+
console.log('Received from server:', data);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Event handler for disconnection
|
|
30
|
+
socket.on('disconnect', () => {
|
|
31
|
+
console.log('Disconnected from server.');
|
|
32
|
+
});
|
package/web/Server.ts
CHANGED
|
@@ -681,6 +681,10 @@ export class HttpServer extends ProtoServer {
|
|
|
681
681
|
logger.error(`Error replaying packet: ${err.message}`);
|
|
682
682
|
}
|
|
683
683
|
});
|
|
684
|
+
sock.on('rawbytes', (data:any)=>{
|
|
685
|
+
let port = conn.findPortById(0);
|
|
686
|
+
port.pushIn(Buffer.from(data));
|
|
687
|
+
})
|
|
684
688
|
sock.on('sendLogMessages', function (sendMessages: boolean) {
|
|
685
689
|
console.log(`sendLogMessages set to ${sendMessages}`);
|
|
686
690
|
if (!sendMessages) sock.leave('msgLogger');
|
|
@@ -1636,7 +1640,7 @@ export class REMInterfaceServer extends ProtoServer {
|
|
|
1636
1640
|
}
|
|
1637
1641
|
return (response.status.code === 200) ? JSON.parse(response.data) : [];
|
|
1638
1642
|
}
|
|
1639
|
-
catch (err) { logger.error(err); }
|
|
1643
|
+
catch (err) { logger.error(`getDevices: ${err.message}`); }
|
|
1640
1644
|
}
|
|
1641
1645
|
}
|
|
1642
1646
|
export class BackupFile {
|
|
@@ -1719,16 +1723,18 @@ export class RestoreFile {
|
|
|
1719
1723
|
this.njsPC.poolConfig = await this.extractFile(zip, 'njsPC/data/poolConfig.json');
|
|
1720
1724
|
this.njsPC.poolState = await this.extractFile(zip, 'njsPC/data/poolState.json');
|
|
1721
1725
|
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1726
|
+
if (typeof this.options.servers !== 'undefined') {
|
|
1727
|
+
for (let i = 0; i < this.options.servers.length; i++) {
|
|
1728
|
+
// Extract each server from the file.
|
|
1729
|
+
let srv = this.options.servers[i];
|
|
1730
|
+
if (srv.backup && srv.success) {
|
|
1731
|
+
this.servers.push({
|
|
1732
|
+
name: srv.name,
|
|
1733
|
+
uuid: srv.uuid,
|
|
1734
|
+
serverConfig: await this.extractFile(zip, `${srv.name}/serverConfig.json`),
|
|
1735
|
+
controllerConfig: await this.extractFile(zip, `${srv.name}/data/controllerConfig.json`)
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1732
1738
|
}
|
|
1733
1739
|
}
|
|
1734
1740
|
} catch(err) { this.errors.push(err); logger.error(`Error extracting restore options from ${file}: ${err.message}`); }
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
}
|
|
35
35
|
],
|
|
36
36
|
"rootTopic-DIRECTIONS": "rootTopic in config.json is ingored. Instead set the two topic variables in the vars section",
|
|
37
|
-
"_rootTopic": "@bind=(state.equipment.model).replace(
|
|
37
|
+
"_rootTopic": "@bind=(state.equipment.model).replace(/ /g,'-').replace(' / ','').toLowerCase();",
|
|
38
38
|
"clientId": "@bind=`hass_njsPC_${webApp.mac().replace(/:/g, '_'}-${webApp.httpPort()}`;"
|
|
39
39
|
}
|
|
40
40
|
},
|
|
@@ -434,4 +434,4 @@
|
|
|
434
434
|
]
|
|
435
435
|
}
|
|
436
436
|
]
|
|
437
|
-
}
|
|
437
|
+
}
|