nodejs-poolcontroller 7.3.0 → 7.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/Changelog +23 -0
- package/README.md +5 -5
- package/app.ts +2 -0
- package/config/Config.ts +3 -0
- package/config/VersionCheck.ts +8 -4
- package/controller/Constants.ts +88 -0
- package/controller/Equipment.ts +246 -66
- package/controller/Errors.ts +24 -1
- package/controller/Lockouts.ts +423 -0
- package/controller/State.ts +314 -54
- package/controller/boards/EasyTouchBoard.ts +107 -59
- package/controller/boards/IntelliCenterBoard.ts +186 -125
- package/controller/boards/IntelliTouchBoard.ts +104 -30
- package/controller/boards/NixieBoard.ts +721 -159
- package/controller/boards/SystemBoard.ts +2370 -1108
- package/controller/comms/Comms.ts +85 -10
- package/controller/comms/messages/Messages.ts +10 -4
- package/controller/comms/messages/config/ChlorinatorMessage.ts +13 -4
- package/controller/comms/messages/config/CircuitGroupMessage.ts +6 -0
- package/controller/comms/messages/config/CoverMessage.ts +1 -0
- package/controller/comms/messages/config/EquipmentMessage.ts +4 -0
- package/controller/comms/messages/config/ExternalMessage.ts +44 -26
- package/controller/comms/messages/config/FeatureMessage.ts +8 -1
- package/controller/comms/messages/config/GeneralMessage.ts +8 -0
- package/controller/comms/messages/config/HeaterMessage.ts +15 -9
- package/controller/comms/messages/config/IntellichemMessage.ts +4 -1
- package/controller/comms/messages/config/OptionsMessage.ts +13 -1
- package/controller/comms/messages/config/PumpMessage.ts +4 -20
- package/controller/comms/messages/config/RemoteMessage.ts +4 -0
- package/controller/comms/messages/config/ScheduleMessage.ts +11 -0
- package/controller/comms/messages/config/SecurityMessage.ts +1 -0
- package/controller/comms/messages/config/ValveMessage.ts +13 -3
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +2 -3
- package/controller/comms/messages/status/EquipmentStateMessage.ts +78 -24
- package/controller/comms/messages/status/HeaterStateMessage.ts +42 -9
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +37 -26
- package/controller/nixie/Nixie.ts +18 -16
- package/controller/nixie/bodies/Body.ts +4 -1
- package/controller/nixie/chemistry/ChemController.ts +80 -77
- package/controller/nixie/chemistry/Chlorinator.ts +9 -8
- package/controller/nixie/circuits/Circuit.ts +55 -6
- package/controller/nixie/heaters/Heater.ts +192 -32
- package/controller/nixie/pumps/Pump.ts +146 -84
- package/controller/nixie/schedules/Schedule.ts +3 -2
- package/controller/nixie/valves/Valve.ts +1 -1
- package/defaultConfig.json +32 -1
- package/issue_template.md +1 -1
- package/logger/DataLogger.ts +37 -22
- package/package.json +20 -18
- package/web/Server.ts +520 -29
- package/web/bindings/influxDB.json +96 -8
- package/web/bindings/mqtt.json +151 -40
- package/web/bindings/mqttAlt.json +114 -4
- package/web/interfaces/httpInterface.ts +2 -0
- package/web/interfaces/influxInterface.ts +36 -19
- package/web/interfaces/mqttInterface.ts +14 -3
- package/web/services/config/Config.ts +171 -44
- package/web/services/state/State.ts +49 -5
- package/web/services/state/StateSocket.ts +18 -1
|
@@ -25,33 +25,46 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
25
25
|
super(cfg);
|
|
26
26
|
}
|
|
27
27
|
private writeApi: WriteApi;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
declare context: InterfaceContext;
|
|
29
|
+
declare cfg;
|
|
30
|
+
declare events: InfluxInterfaceEvent[];
|
|
31
31
|
private init = () => {
|
|
32
32
|
let baseOpts = extend(true, this.cfg.options, this.context.options);
|
|
33
|
+
let url = 'http://';
|
|
34
|
+
if (typeof baseOpts.protocol !== 'undefined' && baseOpts.protocol) url = baseOpts.protocol;
|
|
35
|
+
if (!url.endsWith('://')) url += '://';
|
|
36
|
+
url = `${url}${baseOpts.host}:${baseOpts.port}`;
|
|
37
|
+
let influxDB: InfluxDB;
|
|
38
|
+
let bucket;
|
|
39
|
+
let org;
|
|
33
40
|
if (typeof baseOpts.host === 'undefined' || !baseOpts.host) {
|
|
34
41
|
logger.warn(`Interface: ${this.cfg.name} has not resolved to a valid host.`);
|
|
35
42
|
return;
|
|
36
43
|
}
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
if (baseOpts.version === 1) {
|
|
45
|
+
if (typeof baseOpts.database === 'undefined' || !baseOpts.database) {
|
|
46
|
+
logger.warn(`Interface: ${this.cfg.name} has not resolved to a valid database.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
bucket = `${baseOpts.database}/${baseOpts.retentionPolicy}`;
|
|
50
|
+
const clientOptions: ClientOptions = {
|
|
51
|
+
url,
|
|
52
|
+
token: `${baseOpts.username}:${baseOpts.password}`,
|
|
53
|
+
}
|
|
54
|
+
influxDB = new InfluxDB(clientOptions);
|
|
40
55
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
token: `${baseOpts.username}:${baseOpts.password}`,
|
|
56
|
+
else if (baseOpts.version === 2) {
|
|
57
|
+
org = baseOpts.org;
|
|
58
|
+
bucket = baseOpts.bucket;
|
|
59
|
+
const clientOptions: ClientOptions = {
|
|
60
|
+
url,
|
|
61
|
+
token: baseOpts.token,
|
|
62
|
+
}
|
|
63
|
+
influxDB = new InfluxDB(clientOptions);
|
|
50
64
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
this.writeApi = influxDB.getWriteApi(org, bucket, 'ms');
|
|
66
|
+
|
|
67
|
+
|
|
55
68
|
// set global tags from context
|
|
56
69
|
let baseTags = {}
|
|
57
70
|
baseOpts.tags.forEach(tag => {
|
|
@@ -175,10 +188,14 @@ export class InfluxInterfaceBindings extends BaseInterfaceBindings {
|
|
|
175
188
|
let sec = ts.getSeconds() - 1;
|
|
176
189
|
ts.setSeconds(sec);
|
|
177
190
|
point2.timestamp(ts);
|
|
191
|
+
logger.silly(`Writing influx ${e.name} inverse data point ${point2.toString()})`)
|
|
178
192
|
this.writeApi.writePoint(point2);
|
|
179
193
|
}
|
|
180
194
|
if (typeof point.toLineProtocol() !== 'undefined') {
|
|
195
|
+
logger.silly(`Writing influx ${e.name} data point ${point.toString()}`)
|
|
181
196
|
this.writeApi.writePoint(point);
|
|
197
|
+
this.writeApi.flush()
|
|
198
|
+
.catch(error => { logger.error(error); });
|
|
182
199
|
//logger.info(`INFLUX: ${point.toLineProtocol()}`)
|
|
183
200
|
}
|
|
184
201
|
else {
|
|
@@ -36,7 +36,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
36
36
|
}
|
|
37
37
|
private client: MqttClient;
|
|
38
38
|
private topics: string[] = [];
|
|
39
|
-
|
|
39
|
+
declare events: MqttInterfaceEvent[];
|
|
40
40
|
private subscribed: boolean; // subscribed to events or not
|
|
41
41
|
private sentInitialMessages = false;
|
|
42
42
|
private init = () => {
|
|
@@ -370,7 +370,18 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
370
370
|
logger.error(new ServiceParameterError(`Cannot set body setPoint. You must supply a valid id, circuit, name, or type for the body`, 'body', 'id', msg.id));
|
|
371
371
|
return;
|
|
372
372
|
}
|
|
373
|
-
|
|
373
|
+
if (typeof msg.setPoint !== 'undefined' || typeof msg.heatSetpoint !== 'undefined') {
|
|
374
|
+
let setPoint = parseInt(msg.setPoint, 10) || parseInt(msg.heatSetpoint, 10);
|
|
375
|
+
if (!isNaN(setPoint)) {
|
|
376
|
+
await sys.board.bodies.setHeatSetpointAsync(body, setPoint);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (typeof msg.coolSetpoint !== 'undefined') {
|
|
380
|
+
let setPoint = parseInt(msg.coolSetpoint, 10);
|
|
381
|
+
if (!isNaN(setPoint)) {
|
|
382
|
+
await sys.board.bodies.setCoolSetpointAsync(body, setPoint);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
374
385
|
}
|
|
375
386
|
}
|
|
376
387
|
catch (err) { logger.error(err); }
|
|
@@ -412,7 +423,7 @@ export class MqttInterfaceBindings extends BaseInterfaceBindings {
|
|
|
412
423
|
break;
|
|
413
424
|
case 'settheme':
|
|
414
425
|
try {
|
|
415
|
-
let theme = await state.circuits.setLightThemeAsync(parseInt(msg.id, 10),
|
|
426
|
+
let theme = await state.circuits.setLightThemeAsync(parseInt(msg.id, 10), sys.board.valueMaps.lightThemes.encode(msg.theme));
|
|
416
427
|
}
|
|
417
428
|
catch (err) { logger.error(err); }
|
|
418
429
|
break;
|
|
@@ -14,16 +14,21 @@ GNU Affero General Public License for more details.
|
|
|
14
14
|
You should have received a copy of the GNU Affero General Public License
|
|
15
15
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
|
+
import * as fs from "fs";
|
|
17
18
|
import * as express from "express";
|
|
18
19
|
import * as extend from 'extend';
|
|
20
|
+
import * as multer from 'multer';
|
|
21
|
+
import * as path from "path";
|
|
19
22
|
import { sys, LightGroup, ControllerType, Pump, Valve, Body, General, Circuit, ICircuit, Feature, CircuitGroup, CustomNameCollection, Schedule, Chlorinator, Heater } from "../../../controller/Equipment";
|
|
20
23
|
import { config } from "../../../config/Config";
|
|
21
24
|
import { logger } from "../../../logger/Logger";
|
|
22
25
|
import { utils } from "../../../controller/Constants";
|
|
26
|
+
import { ServiceProcessError } from "../../../controller/Errors";
|
|
23
27
|
import { state } from "../../../controller/State";
|
|
24
28
|
import { stopPacketCaptureAsync, startPacketCapture } from '../../../app';
|
|
25
29
|
import { conn } from "../../../controller/comms/Comms";
|
|
26
|
-
import { webApp } from "../../Server";
|
|
30
|
+
import { webApp, BackupFile, RestoreFile } from "../../Server";
|
|
31
|
+
import { release } from "os";
|
|
27
32
|
|
|
28
33
|
export class ConfigRoute {
|
|
29
34
|
public static initRoutes(app: express.Application) {
|
|
@@ -52,7 +57,8 @@ export class ConfigRoute {
|
|
|
52
57
|
clockSources: sys.board.valueMaps.clockSources.toArray(),
|
|
53
58
|
clockModes: sys.board.valueMaps.clockModes.toArray(),
|
|
54
59
|
pool: sys.general.get(true),
|
|
55
|
-
sensors: sys.board.system.getSensors()
|
|
60
|
+
sensors: sys.board.system.getSensors(),
|
|
61
|
+
systemUnits: sys.board.valueMaps.systemUnits.toArray()
|
|
56
62
|
};
|
|
57
63
|
return res.status(200).send(opts);
|
|
58
64
|
});
|
|
@@ -106,7 +112,7 @@ export class ConfigRoute {
|
|
|
106
112
|
invalidIds: sys.board.equipmentIds.invalidIds.get(),
|
|
107
113
|
equipmentIds: sys.equipment.equipmentIds.features,
|
|
108
114
|
equipmentNames: sys.board.circuits.getCircuitNames(),
|
|
109
|
-
functions: sys.board.
|
|
115
|
+
functions: sys.board.features.getFeatureFunctions(),
|
|
110
116
|
features: sys.features.get()
|
|
111
117
|
};
|
|
112
118
|
return res.status(200).send(opts);
|
|
@@ -115,7 +121,8 @@ export class ConfigRoute {
|
|
|
115
121
|
let opts = {
|
|
116
122
|
maxBodies: sys.equipment.maxBodies,
|
|
117
123
|
bodyTypes: sys.board.valueMaps.bodies.toArray(),
|
|
118
|
-
bodies: sys.bodies.get()
|
|
124
|
+
bodies: sys.bodies.get(),
|
|
125
|
+
capacityUnits: sys.board.valueMaps.volumeUnits.toArray()
|
|
119
126
|
};
|
|
120
127
|
return res.status(200).send(opts);
|
|
121
128
|
});
|
|
@@ -282,7 +289,8 @@ export class ConfigRoute {
|
|
|
282
289
|
bodies: sys.board.bodies.getBodyAssociations(),
|
|
283
290
|
chlorinators: sys.chlorinators.get(),
|
|
284
291
|
maxChlorinators: sys.equipment.maxChlorinators,
|
|
285
|
-
models: sys.board.valueMaps.chlorinatorModel.toArray()
|
|
292
|
+
models: sys.board.valueMaps.chlorinatorModel.toArray(),
|
|
293
|
+
equipmentMasters: sys.board.valueMaps.equipmentMaster.toArray()
|
|
286
294
|
};
|
|
287
295
|
return res.status(200).send(opts);
|
|
288
296
|
});
|
|
@@ -307,13 +315,16 @@ export class ConfigRoute {
|
|
|
307
315
|
let opts = {
|
|
308
316
|
interfaces: config.getSection('web.interfaces'),
|
|
309
317
|
types: [
|
|
310
|
-
{name: '
|
|
311
|
-
{name: '
|
|
318
|
+
{ name: 'rest', desc: 'Rest' },
|
|
319
|
+
{ name: 'http', desc: 'Http' },
|
|
320
|
+
{ name: 'rem', desc: 'Relay Equipment Manager' },
|
|
321
|
+
{ name: 'mqtt', desc: 'MQTT' },
|
|
322
|
+
{ name: 'influx', desc: 'InfluxDB' }
|
|
312
323
|
],
|
|
313
324
|
protocols: [
|
|
314
325
|
{ val: 0, name: 'http://', desc: 'http://' },
|
|
315
326
|
{ val: 1, name: 'https://', desc: 'https://' },
|
|
316
|
-
{ val: 2, name: 'mqtt://', desc: 'mqtt://' }
|
|
327
|
+
{ val: 2, name: 'mqtt://', desc: 'mqtt://' }
|
|
317
328
|
]
|
|
318
329
|
}
|
|
319
330
|
return res.status(200).send(opts);
|
|
@@ -332,6 +343,8 @@ export class ConfigRoute {
|
|
|
332
343
|
bodies: sys.board.bodies.getBodyAssociations(),
|
|
333
344
|
filters: sys.filters.get(),
|
|
334
345
|
areaUnits: sys.board.valueMaps.areaUnits.toArray(),
|
|
346
|
+
pressureUnits: sys.board.valueMaps.pressureUnits.toArray(),
|
|
347
|
+
circuits: sys.board.circuits.getCircuitReferences(true, true, true, false),
|
|
335
348
|
servers: []
|
|
336
349
|
};
|
|
337
350
|
if (sys.controllerType === ControllerType.Nixie) opts.servers = await sys.ncp.getREMServers();
|
|
@@ -340,14 +353,14 @@ export class ConfigRoute {
|
|
|
340
353
|
});
|
|
341
354
|
/******* END OF CONFIGURATION PICK LISTS/REFERENCES AND VALIDATION ***********/
|
|
342
355
|
/******* ENDPOINTS FOR MODIFYING THE OUTDOOR CONTROL PANEL SETTINGS **********/
|
|
343
|
-
app.put('/config/rem', async (req, res, next)=>{
|
|
356
|
+
app.put('/config/rem', async (req, res, next) => {
|
|
344
357
|
try {
|
|
345
358
|
// RSG: this is problematic because we now enable multiple rem type interfaces that may not be called REM.
|
|
346
359
|
// This is now also a dupe of PUT /app/interface and should be consolidated
|
|
347
360
|
// config.setSection('web.interfaces.rem', req.body);
|
|
348
361
|
config.setInterface(req.body);
|
|
349
362
|
}
|
|
350
|
-
catch (err) {next(err);}
|
|
363
|
+
catch (err) { next(err); }
|
|
351
364
|
})
|
|
352
365
|
app.put('/config/tempSensors', async (req, res, next) => {
|
|
353
366
|
try {
|
|
@@ -362,7 +375,7 @@ export class ConfigRoute {
|
|
|
362
375
|
});
|
|
363
376
|
app.put('/config/filter', async (req, res, next) => {
|
|
364
377
|
try {
|
|
365
|
-
let sfilter = sys.board.filters.
|
|
378
|
+
let sfilter = await sys.board.filters.setFilterAsync(req.body);
|
|
366
379
|
return res.status(200).send(sfilter.get(true));
|
|
367
380
|
}
|
|
368
381
|
catch (err) { next(err); }
|
|
@@ -375,7 +388,7 @@ export class ConfigRoute {
|
|
|
375
388
|
});
|
|
376
389
|
app.delete('/config/filter', async (req, res, next) => {
|
|
377
390
|
try {
|
|
378
|
-
let sfilter = sys.board.filters.
|
|
391
|
+
let sfilter = await sys.board.filters.deleteFilterAsync(req.body);
|
|
379
392
|
return res.status(200).send(sfilter.get(true));
|
|
380
393
|
}
|
|
381
394
|
catch (err) { next(err); }
|
|
@@ -579,8 +592,8 @@ export class ConfigRoute {
|
|
|
579
592
|
return res.status(200).send(circuitFunctions);
|
|
580
593
|
});
|
|
581
594
|
app.get('/config/features/functions', (req, res) => {
|
|
582
|
-
let
|
|
583
|
-
return res.status(200).send(
|
|
595
|
+
let featureFunctions = sys.board.features.getFeatureFunctions();
|
|
596
|
+
return res.status(200).send(featureFunctions);
|
|
584
597
|
});
|
|
585
598
|
app.get('/config/circuit/:id', (req, res) => {
|
|
586
599
|
// todo: need getInterfaceById.get() in case features are requested here
|
|
@@ -589,7 +602,7 @@ export class ConfigRoute {
|
|
|
589
602
|
});
|
|
590
603
|
app.get('/config/circuit/:id/lightThemes', (req, res) => {
|
|
591
604
|
let circuit = sys.circuits.getInterfaceById(parseInt(req.params.id, 10));
|
|
592
|
-
let themes = typeof circuit !== 'undefined' && typeof circuit.getLightThemes === 'function' ? circuit.getLightThemes() : [];
|
|
605
|
+
let themes = typeof circuit !== 'undefined' && typeof circuit.getLightThemes === 'function' ? circuit.getLightThemes(circuit.type) : [];
|
|
593
606
|
return res.status(200).send(themes);
|
|
594
607
|
});
|
|
595
608
|
app.get('/config/chlorinator/:id', (req, res) => {
|
|
@@ -727,7 +740,7 @@ export class ConfigRoute {
|
|
|
727
740
|
// RSG: is this and /config/circuit/:id/lightThemes both needed?
|
|
728
741
|
|
|
729
742
|
// if (sys.controllerType === ControllerType.IntelliCenter) {
|
|
730
|
-
let grp = sys.lightGroups.getItemById(parseInt(req.
|
|
743
|
+
let grp = sys.lightGroups.getItemById(parseInt(req.body.id, 10));
|
|
731
744
|
return res.status(200).send(grp.getLightThemes());
|
|
732
745
|
// }
|
|
733
746
|
// else
|
|
@@ -760,16 +773,16 @@ export class ConfigRoute {
|
|
|
760
773
|
let grp = sys.circuitGroups.getItemById(parseInt(req.params.id, 10));
|
|
761
774
|
return res.status(200).send(grp.getExtended());
|
|
762
775
|
});
|
|
763
|
-
/* app.get('/config/chemController/search', async (req, res, next) => {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
776
|
+
/* app.get('/config/chemController/search', async (req, res, next) => {
|
|
777
|
+
// Change the options for the pool.
|
|
778
|
+
try {
|
|
779
|
+
let result = await sys.board.virtualChemControllers.search();
|
|
780
|
+
return res.status(200).send(result);
|
|
781
|
+
}
|
|
782
|
+
catch (err) {
|
|
783
|
+
next(err);
|
|
784
|
+
}
|
|
785
|
+
}); */
|
|
773
786
|
app.put('/config/chemController', async (req, res, next) => {
|
|
774
787
|
try {
|
|
775
788
|
let chem = await sys.board.chemControllers.setChemControllerAsync(req.body);
|
|
@@ -792,17 +805,17 @@ export class ConfigRoute {
|
|
|
792
805
|
catch (err) { next(err); }
|
|
793
806
|
|
|
794
807
|
});
|
|
795
|
-
/* app.get('/config/intellibrite', (req, res) => {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
808
|
+
/* app.get('/config/intellibrite', (req, res) => {
|
|
809
|
+
return res.status(200).send(sys.intellibrite.getExtended());
|
|
810
|
+
});
|
|
811
|
+
app.get('/config/intellibrite/colors', (req, res) => {
|
|
812
|
+
return res.status(200).send(sys.board.valueMaps.lightColors.toArray());
|
|
813
|
+
});
|
|
814
|
+
app.put('/config/intellibrite/setColors', (req, res) => {
|
|
815
|
+
let grp = extend(true, { id: 0 }, req.body);
|
|
816
|
+
sys.board.circuits.setIntelliBriteColors(new LightGroup(grp));
|
|
817
|
+
return res.status(200).send('OK');
|
|
818
|
+
}); */
|
|
806
819
|
app.get('/config', (req, res) => {
|
|
807
820
|
return res.status(200).send(sys.getSection('all'));
|
|
808
821
|
});
|
|
@@ -828,11 +841,11 @@ export class ConfigRoute {
|
|
|
828
841
|
return res.status(200).send('OK');
|
|
829
842
|
});
|
|
830
843
|
app.put('/app/interface', async (req, res, next) => {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
844
|
+
try {
|
|
845
|
+
let iface = await webApp.updateServerInterface(req.body);
|
|
846
|
+
return res.status(200).send(iface);
|
|
847
|
+
}
|
|
848
|
+
catch (err) { next(err); }
|
|
836
849
|
});
|
|
837
850
|
app.put('/app/rs485Port', async (req, res, next) => {
|
|
838
851
|
try {
|
|
@@ -849,15 +862,129 @@ export class ConfigRoute {
|
|
|
849
862
|
startPacketCapture(false);
|
|
850
863
|
return res.status(200).send('OK');
|
|
851
864
|
});
|
|
852
|
-
app.get('/app/config/stopPacketCapture', async (req, res,next) => {
|
|
865
|
+
app.get('/app/config/stopPacketCapture', async (req, res, next) => {
|
|
853
866
|
try {
|
|
854
867
|
let file = await stopPacketCaptureAsync();
|
|
855
868
|
res.download(file);
|
|
856
869
|
}
|
|
857
|
-
catch (err) {next(err);}
|
|
870
|
+
catch (err) { next(err); }
|
|
858
871
|
});
|
|
859
872
|
app.get('/app/config/:section', (req, res) => {
|
|
860
873
|
return res.status(200).send(config.getSection(req.params.section));
|
|
861
874
|
});
|
|
875
|
+
app.get('/app/config/options/backup', async (req, res, next) => {
|
|
876
|
+
try {
|
|
877
|
+
let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0 }, keepCount: 5, servers: [] });
|
|
878
|
+
let servers = await sys.ncp.getREMServers();
|
|
879
|
+
if (typeof servers !== 'undefined') {
|
|
880
|
+
// Just in case somebody deletes the backup section and doesn't put it back properly.
|
|
881
|
+
for (let i = 0; i < servers.length; i++) {
|
|
882
|
+
let srv = servers[i];
|
|
883
|
+
if (typeof opts.servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.push({ name: srv.name, uuid: srv.uuid, backup: false, host: srv.interface.options.host });
|
|
884
|
+
}
|
|
885
|
+
for (let i = opts.servers.length - 1; i >= 0; i--) {
|
|
886
|
+
let srv = opts.servers[i];
|
|
887
|
+
if (typeof servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.splice(i, 1);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (typeof opts.servers === 'undefined') opts.servers = [];
|
|
891
|
+
return res.status(200).send(opts);
|
|
892
|
+
} catch (err) { next(err); }
|
|
893
|
+
});
|
|
894
|
+
app.get('/app/config/options/restore', async (req, res, next) => {
|
|
895
|
+
try {
|
|
896
|
+
let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0 }, keepCount: 5, servers: [], backupFiles: [] });
|
|
897
|
+
let servers = await sys.ncp.getREMServers();
|
|
898
|
+
if (typeof servers !== 'undefined') {
|
|
899
|
+
for (let i = 0; i < servers.length; i++) {
|
|
900
|
+
let srv = servers[i];
|
|
901
|
+
if (typeof opts.servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.push({ name: srv.name, uuid: srv.uuid, backup: false });
|
|
902
|
+
}
|
|
903
|
+
for (let i = opts.servers.length - 1; i >= 0; i--) {
|
|
904
|
+
let srv = opts.servers[i];
|
|
905
|
+
if (typeof servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.splice(i, 1);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (typeof opts.servers === 'undefined') opts.servers = [];
|
|
909
|
+
opts.backupFiles = await webApp.readBackupFiles();
|
|
910
|
+
return res.status(200).send(opts);
|
|
911
|
+
} catch (err) { next(err); }
|
|
912
|
+
|
|
913
|
+
});
|
|
914
|
+
app.put('/app/config/options/backup', async (req, res, next) => {
|
|
915
|
+
try {
|
|
916
|
+
config.setSection('controller.backups', req.body);
|
|
917
|
+
let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0 }, keepCount: 5, servers: [] });
|
|
918
|
+
webApp.autoBackup = utils.makeBool(opts.automatic);
|
|
919
|
+
await webApp.checkAutoBackup();
|
|
920
|
+
return res.status(200).send(opts);
|
|
921
|
+
} catch (err) { next(err); }
|
|
922
|
+
|
|
923
|
+
});
|
|
924
|
+
app.put('/app/config/createBackup', async (req, res, next) => {
|
|
925
|
+
try {
|
|
926
|
+
let ret = await webApp.backupServer(req.body);
|
|
927
|
+
res.download(ret.filePath);
|
|
928
|
+
}
|
|
929
|
+
catch (err) { next(err); }
|
|
930
|
+
});
|
|
931
|
+
app.delete('/app/backup/file', async (req, res, next) => {
|
|
932
|
+
try {
|
|
933
|
+
let opts = req.body;
|
|
934
|
+
fs.unlinkSync(opts.filePath);
|
|
935
|
+
return res.status(200).send(opts);
|
|
936
|
+
}
|
|
937
|
+
catch (err) { next(err); }
|
|
938
|
+
});
|
|
939
|
+
app.post('/app/backup/file', async (req, res, next) => {
|
|
940
|
+
try {
|
|
941
|
+
let file = multer({
|
|
942
|
+
limits: { fileSize: 1000000 },
|
|
943
|
+
storage: multer.memoryStorage()
|
|
944
|
+
}).single('backupFile');
|
|
945
|
+
file(req, res, async (err) => {
|
|
946
|
+
try {
|
|
947
|
+
if (err) { next(err); }
|
|
948
|
+
else {
|
|
949
|
+
// Validate the incoming data and save it off only if it is valid.
|
|
950
|
+
let bf = await BackupFile.fromBuffer(req.file.originalname, req.file.buffer);
|
|
951
|
+
if (typeof bf === 'undefined') {
|
|
952
|
+
err = new ServiceProcessError(`Invalid backup file: ${req.file.originalname}`, 'POST: app/backup/file', 'extractBackupOptions');
|
|
953
|
+
next(err);
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
if (fs.existsSync(bf.filePath))
|
|
957
|
+
return next(new ServiceProcessError(`File already exists ${req.file.originalname}`, 'POST: app/backup/file', 'writeFile'));
|
|
958
|
+
else {
|
|
959
|
+
try {
|
|
960
|
+
fs.writeFileSync(bf.filePath, req.file.buffer);
|
|
961
|
+
} catch (e) { logger.error(`Error writing backup file ${e.message}`); }
|
|
962
|
+
}
|
|
963
|
+
return res.status(200).send(bf);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
} catch (e) {
|
|
967
|
+
err = new ServiceProcessError(`Error uploading file: ${e.message}`, 'POST: app/backup/file', 'uploadFile');
|
|
968
|
+
next(err);
|
|
969
|
+
logger.error(e);
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
} catch (err) { next(err); }
|
|
973
|
+
});
|
|
974
|
+
app.put('/app/restore/validate', async (req, res, next) => {
|
|
975
|
+
try {
|
|
976
|
+
// Validate all the restore options.
|
|
977
|
+
let opts = req.body;
|
|
978
|
+
let ctx = await webApp.validateRestore(opts);
|
|
979
|
+
return res.status(200).send(ctx);
|
|
980
|
+
} catch (err) { next(err); }
|
|
981
|
+
});
|
|
982
|
+
app.put('/app/restore/file', async (req, res, next) => {
|
|
983
|
+
try {
|
|
984
|
+
let opts = req.body;
|
|
985
|
+
let results = await webApp.restoreServers(opts);
|
|
986
|
+
return res.status(200).send(results);
|
|
987
|
+
} catch (err) { next(err); }
|
|
988
|
+
});
|
|
862
989
|
}
|
|
863
990
|
}
|
|
@@ -15,6 +15,8 @@ You should have received a copy of the GNU Affero General Public License
|
|
|
15
15
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
import * as express from "express";
|
|
18
|
+
import * as extend from "extend";
|
|
19
|
+
|
|
18
20
|
import { state, ICircuitState, LightGroupState, ICircuitGroupState, ChemicalDoseState } from "../../../controller/State";
|
|
19
21
|
import { sys } from "../../../controller/Equipment";
|
|
20
22
|
import { utils } from '../../../controller/Constants';
|
|
@@ -79,7 +81,21 @@ export class StateRoute {
|
|
|
79
81
|
catch (err) { next(err); }
|
|
80
82
|
|
|
81
83
|
});
|
|
82
|
-
app.
|
|
84
|
+
app.get('/state/chemController/:id/doseLog/ph', async (req, res, next) => {
|
|
85
|
+
try {
|
|
86
|
+
let schem = state.chemControllers.getItemById(parseInt(req.params.id));
|
|
87
|
+
let filter = req.body || {};
|
|
88
|
+
let dh = await DataLogger.readFromEndAsync(`chemDosage_${schem.ph.chemType}.log`, ChemicalDoseState, (lineNumber: number, entry: ChemicalDoseState, arr: ChemicalDoseState[]): boolean => {
|
|
89
|
+
if (entry.id !== schem.id) return false;
|
|
90
|
+
if (typeof filter.lines !== 'undefined' && filter.lines <= arr.length) return false;
|
|
91
|
+
if (typeof filter.date !== 'undefined' && entry.end < filter.date) return false;
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
return res.status(200).send(dh);
|
|
95
|
+
}
|
|
96
|
+
catch (err) { next(err); }
|
|
97
|
+
});
|
|
98
|
+
app.search('/state/chemController/:id/doseLog/ph', async (req, res, next) => {
|
|
83
99
|
try {
|
|
84
100
|
let schem = state.chemControllers.getItemById(parseInt(req.params.id));
|
|
85
101
|
let filter = req.body || {};
|
|
@@ -93,6 +109,34 @@ export class StateRoute {
|
|
|
93
109
|
}
|
|
94
110
|
catch (err) { next(err); }
|
|
95
111
|
});
|
|
112
|
+
app.get('/state/chemController/:id/doseLog/orp', async (req, res, next) => {
|
|
113
|
+
try {
|
|
114
|
+
let schem = state.chemControllers.getItemById(parseInt(req.params.id));
|
|
115
|
+
let filter = req.body || {};
|
|
116
|
+
let dh = await DataLogger.readFromEndAsync(`chemDosage_orp.log`, ChemicalDoseState, (lineNumber: number, entry: ChemicalDoseState, arr: ChemicalDoseState[]): boolean => {
|
|
117
|
+
if (entry.id !== schem.id) return false;
|
|
118
|
+
if (typeof filter.lines !== 'undefined' && filter.lines <= arr.length) return false;
|
|
119
|
+
if (typeof filter.date !== 'undefined' && entry.end < filter.date) return false;
|
|
120
|
+
return true;
|
|
121
|
+
});
|
|
122
|
+
return res.status(200).send(dh);
|
|
123
|
+
}
|
|
124
|
+
catch (err) { next(err); }
|
|
125
|
+
});
|
|
126
|
+
app.search('/state/chemController/:id/doseLog/orp', async (req, res, next) => {
|
|
127
|
+
try {
|
|
128
|
+
let schem = state.chemControllers.getItemById(parseInt(req.params.id));
|
|
129
|
+
let filter = req.body || {};
|
|
130
|
+
let dh = DataLogger.readFromEnd(`chemDosage_orp.log`, ChemicalDoseState, (lineNumber: number, entry: ChemicalDoseState, arr: ChemicalDoseState[]): boolean => {
|
|
131
|
+
if (entry.id !== schem.id) return;
|
|
132
|
+
if (typeof filter.lines !== 'undefined' && filter.lines <= arr.length) return false;
|
|
133
|
+
if (typeof filter.date !== 'undefined' && entry.end < filter.date) return false;
|
|
134
|
+
return true;
|
|
135
|
+
});
|
|
136
|
+
return res.status(200).send(dh);
|
|
137
|
+
}
|
|
138
|
+
catch (err) { next(err); }
|
|
139
|
+
});
|
|
96
140
|
app.put('/state/chemController/cancelDosing', async (req, res, next) => {
|
|
97
141
|
try {
|
|
98
142
|
let schem = await sys.board.chemControllers.cancelDosingAsync(req.body);
|
|
@@ -109,7 +153,7 @@ export class StateRoute {
|
|
|
109
153
|
});
|
|
110
154
|
|
|
111
155
|
app.get('/state/chlorinator/:id', (req, res) => {
|
|
112
|
-
res.status(200).send(state.chlorinators.getItemById(parseInt(req.params.id, 10)).
|
|
156
|
+
res.status(200).send(state.chlorinators.getItemById(parseInt(req.params.id, 10), false).getExtended());
|
|
113
157
|
});
|
|
114
158
|
app.get('/state/circuit/:id', (req, res) => {
|
|
115
159
|
res.status(200).send(state.circuits.getItemById(parseInt(req.params.id, 10)).get());
|
|
@@ -165,8 +209,8 @@ export class StateRoute {
|
|
|
165
209
|
catch (err) {next(err);}
|
|
166
210
|
});
|
|
167
211
|
app.put('/state/circuit/setTheme', async (req, res, next) => {
|
|
168
|
-
|
|
169
|
-
|
|
212
|
+
try {
|
|
213
|
+
let theme = await state.circuits.setLightThemeAsync(parseInt(req.body.id, 10), sys.board.valueMaps.lightThemes.encode(req.body.theme));
|
|
170
214
|
return res.status(200).send(theme.get(true));
|
|
171
215
|
}
|
|
172
216
|
catch (err) { next(err); }
|
|
@@ -229,7 +273,7 @@ export class StateRoute {
|
|
|
229
273
|
if (typeof req.body.heatSetpoint !== 'undefined' && !isNaN(parseInt(req.body.heatSetpoint, 10)))
|
|
230
274
|
await sys.board.bodies.setHeatSetpointAsync(body, parseInt(req.body.heatSetpoint, 10));
|
|
231
275
|
else if (typeof req.body.setPoint !== 'undefined' && !isNaN(parseInt(req.body.setPoint, 10)))
|
|
232
|
-
await sys.board.bodies.setHeatSetpointAsync(body, parseInt(req.body.
|
|
276
|
+
await sys.board.bodies.setHeatSetpointAsync(body, parseInt(req.body.setpoint, 10));
|
|
233
277
|
let tbody = state.temps.bodies.getItemById(body.id);
|
|
234
278
|
return res.status(200).send(tbody.get(true));
|
|
235
279
|
} catch (err) { next(err); }
|
|
@@ -74,6 +74,7 @@ export class StateSocket {
|
|
|
74
74
|
}
|
|
75
75
|
catch (err) { logger.error(err); }
|
|
76
76
|
});
|
|
77
|
+
|
|
77
78
|
sock.on('/chlorinator', async (data: any) => {
|
|
78
79
|
try {
|
|
79
80
|
data = JSON.parse(data);
|
|
@@ -99,6 +100,22 @@ export class StateSocket {
|
|
|
99
100
|
}
|
|
100
101
|
catch (err) { logger.error(err); }
|
|
101
102
|
});
|
|
103
|
+
sock.on('/filter', async (data: any) => {
|
|
104
|
+
try {
|
|
105
|
+
data = JSON.parse(data);
|
|
106
|
+
let id = parseInt(data.id, 10);
|
|
107
|
+
let filter = sys.filters.find(elem => elem.id === id);
|
|
108
|
+
if (typeof filter !== 'undefined' && filter.isActive && !isNaN(filter.id)) {
|
|
109
|
+
let sfilter = state.filters.getItemById(filter.id, filter.isActive)
|
|
110
|
+
let pu = sys.board.valueMaps.pressureUnits.transform(filter.pressureUnits);
|
|
111
|
+
if (typeof data.pressure !== 'undefined')
|
|
112
|
+
await sys.board.filters.setFilterPressure(filter.id, data.pressure, data.pressureUnits || pu.name);
|
|
113
|
+
sfilter.emitEquipmentChange();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
} catch (err) { logger.error(err); }
|
|
118
|
+
});
|
|
102
119
|
sock.on('/chemController', async (data: any) => {
|
|
103
120
|
try {
|
|
104
121
|
//console.log(`chemController: ${data}`);
|
|
@@ -144,7 +161,7 @@ export class StateSocket {
|
|
|
144
161
|
if (typeof data.orpTank.units === 'string') scontroller.orp.tank.units = controller.orp.tank.units = data.orpTank.units;
|
|
145
162
|
}
|
|
146
163
|
|
|
147
|
-
// Need to build this out to include the type of controller. If this is
|
|
164
|
+
// Need to build this out to include the type of controller. If this is REM Chem we
|
|
148
165
|
// will send the whole rest of the nut over to it. Intellichem will only let us
|
|
149
166
|
// set specific values.
|
|
150
167
|
if (controller.type === 3) {
|