nodejs-poolcontroller 8.1.2 → 8.4.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 (106) hide show
  1. package/.eslintrc.json +36 -36
  2. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +84 -84
  3. package/.github/ISSUE_TEMPLATE/2-docs.md +12 -12
  4. package/.github/ISSUE_TEMPLATE/3-proposal.md +28 -28
  5. package/.github/ISSUE_TEMPLATE/config.yml +8 -8
  6. package/.github/copilot-instructions.md +63 -0
  7. package/.github/workflows/ghcr-publish.yml +67 -0
  8. package/AGENTS.md +597 -0
  9. package/CONTRIBUTING.md +74 -74
  10. package/Changelog +292 -257
  11. package/Dockerfile +62 -19
  12. package/Gruntfile.js +40 -40
  13. package/LICENSE +661 -661
  14. package/README.md +318 -191
  15. package/anslq25/MessagesMock.ts +221 -221
  16. package/anslq25/boards/MockBoardFactory.ts +49 -49
  17. package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
  18. package/anslq25/boards/MockSystemBoard.ts +216 -216
  19. package/anslq25/chemistry/MockChlorinator.ts +98 -98
  20. package/anslq25/pumps/MockPump.ts +83 -83
  21. package/app.ts +115 -115
  22. package/config/Config.ts +57 -7
  23. package/config/VersionCheck.ts +63 -35
  24. package/controller/Constants.ts +809 -805
  25. package/controller/Equipment.ts +2688 -2664
  26. package/controller/Errors.ts +181 -181
  27. package/controller/Lockouts.ts +549 -549
  28. package/controller/State.ts +3738 -3690
  29. package/controller/boards/AquaLinkBoard.ts +1003 -1003
  30. package/controller/boards/BoardFactory.ts +53 -53
  31. package/controller/boards/EasyTouchBoard.ts +3202 -3202
  32. package/controller/boards/IntelliCenterBoard.ts +4393 -3899
  33. package/controller/boards/IntelliComBoard.ts +69 -69
  34. package/controller/boards/IntelliTouchBoard.ts +382 -382
  35. package/controller/boards/NixieBoard.ts +1944 -1929
  36. package/controller/boards/SunTouchBoard.ts +400 -400
  37. package/controller/boards/SystemBoard.ts +5268 -5268
  38. package/controller/comms/Comms.ts +1272 -1214
  39. package/controller/comms/ScreenLogic.ts +1665 -1665
  40. package/controller/comms/messages/Messages.ts +1433 -1243
  41. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
  42. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  43. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  44. package/controller/comms/messages/config/ConfigMessage.ts +6 -0
  45. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  46. package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
  47. package/controller/comms/messages/config/EquipmentMessage.ts +216 -210
  48. package/controller/comms/messages/config/ExternalMessage.ts +96 -10
  49. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  50. package/controller/comms/messages/config/GeneralMessage.ts +0 -0
  51. package/controller/comms/messages/config/HeaterMessage.ts +0 -0
  52. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  53. package/controller/comms/messages/config/OptionsMessage.ts +194 -174
  54. package/controller/comms/messages/config/PumpMessage.ts +0 -0
  55. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  56. package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
  57. package/controller/comms/messages/config/SecurityMessage.ts +0 -0
  58. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  59. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  60. package/controller/comms/messages/status/EquipmentStateMessage.ts +1158 -822
  61. package/controller/comms/messages/status/HeaterStateMessage.ts +135 -135
  62. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
  63. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
  64. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  65. package/controller/comms/messages/status/RegalModbusStateMessage.ts +411 -0
  66. package/controller/comms/messages/status/VersionMessage.ts +103 -41
  67. package/controller/nixie/Nixie.ts +173 -173
  68. package/controller/nixie/NixieEquipment.ts +104 -104
  69. package/controller/nixie/bodies/Body.ts +120 -120
  70. package/controller/nixie/bodies/Filter.ts +135 -135
  71. package/controller/nixie/chemistry/ChemController.ts +2724 -2724
  72. package/controller/nixie/chemistry/ChemDoser.ts +806 -806
  73. package/controller/nixie/chemistry/Chlorinator.ts +367 -367
  74. package/controller/nixie/circuits/Circuit.ts +478 -478
  75. package/controller/nixie/heaters/Heater.ts +834 -834
  76. package/controller/nixie/pumps/Pump.ts +1194 -996
  77. package/controller/nixie/schedules/Schedule.ts +401 -401
  78. package/controller/nixie/valves/Valve.ts +170 -170
  79. package/defaultConfig.json +352 -347
  80. package/docker-compose.yml +32 -0
  81. package/logger/DataLogger.ts +448 -448
  82. package/logger/Logger.ts +448 -436
  83. package/package.json +58 -60
  84. package/sendSocket.js +32 -32
  85. package/tsconfig.json +25 -25
  86. package/types/express-multer.d.ts +32 -0
  87. package/web/Server.ts +1937 -1927
  88. package/web/bindings/aqualinkD.json +559 -559
  89. package/web/bindings/influxDB.json +1066 -1066
  90. package/web/bindings/mqtt.json +721 -721
  91. package/web/bindings/mqttAlt.json +746 -746
  92. package/web/bindings/rulesManager.json +54 -54
  93. package/web/bindings/smartThings-Hubitat.json +31 -31
  94. package/web/bindings/valveRelays.json +20 -20
  95. package/web/bindings/vera.json +25 -25
  96. package/web/interfaces/baseInterface.ts +188 -188
  97. package/web/interfaces/httpInterface.ts +148 -148
  98. package/web/interfaces/influxInterface.ts +283 -283
  99. package/web/interfaces/mqttInterface.ts +695 -695
  100. package/web/interfaces/ruleInterface.ts +101 -87
  101. package/web/services/config/Config.ts +1063 -1053
  102. package/web/services/config/ConfigSocket.ts +0 -0
  103. package/web/services/state/State.ts +0 -0
  104. package/web/services/state/StateSocket.ts +0 -0
  105. package/web/services/utilities/Utilities.ts +233 -233
  106. package/.github/workflows/docker-publish-njsPC-linux.yml +0 -50
@@ -1,1054 +1,1064 @@
1
- /* nodejs-poolController. An application to control pool equipment.
2
- Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
- Russell Goldin, tagyoureit. russ.goldin@gmail.com
4
-
5
- This program is free software: you can redistribute it and/or modify
6
- it under the terms of the GNU Affero General Public License as
7
- published by the Free Software Foundation, either version 3 of the
8
- License, or (at your option) any later version.
9
-
10
- This program is distributed in the hope that it will be useful,
11
- but WITHOUT ANY WARRANTY; without even the implied warranty of
12
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
- GNU Affero General Public License for more details.
14
-
15
- You should have received a copy of the GNU Affero General Public License
16
- along with this program. If not, see <http://www.gnu.org/licenses/>.
17
- */
18
- import * as fs from "fs";
19
- import * as path from "path";
20
- import * as express from "express";
21
- import * as extend from 'extend';
22
- import * as multer from 'multer';
23
- import { sys, LightGroup, ControllerType, Pump, Valve, Body, General, Circuit, ICircuit, Feature, CircuitGroup, CustomNameCollection, Schedule, Chlorinator, Heater, Screenlogic } from "../../../controller/Equipment";
24
- import { config } from "../../../config/Config";
25
- import { logger } from "../../../logger/Logger";
26
- import { utils } from "../../../controller/Constants";
27
- import { ServiceProcessError } from "../../../controller/Errors";
28
- import { state } from "../../../controller/State";
29
- import { stopPacketCaptureAsync, startPacketCapture } from '../../../app';
30
- import { conn } from "../../../controller/comms/Comms";
31
- import { webApp, BackupFile, RestoreFile } from "../../Server";
32
- import { release } from "os";
33
- import { ScreenLogicComms, sl } from "../../../controller/comms/ScreenLogic";
34
- import { screenlogic } from "node-screenlogic";
35
-
36
- export class ConfigRoute {
37
- public static initRoutes(app: express.Application) {
38
- app.get('/config/body/:body/heatModes', (req, res) => {
39
- return res.status(200).send(sys.bodies.getItemById(parseInt(req.params.body, 10)).getHeatModes());
40
- });
41
- app.get('/config/circuit/names', (req, res) => {
42
- let circuitNames = sys.board.circuits.getCircuitNames();
43
- return res.status(200).send(circuitNames);
44
- });
45
- app.get('/config/circuit/references', (req, res) => {
46
- let circuits = typeof req.query.circuits === 'undefined' || utils.makeBool(req.query.circuits);
47
- let features = typeof req.query.features === 'undefined' || utils.makeBool(req.query.features);
48
- let groups = typeof req.query.features === 'undefined' || utils.makeBool(req.query.groups);
49
- let virtual = typeof req.query.virtual === 'undefined' || utils.makeBool(req.query.virtual);
50
- return res.status(200).send(sys.board.circuits.getCircuitReferences(circuits, features, virtual, groups));
51
- });
52
-
53
- /******* CONFIGURATION PICK LISTS/REFERENCES and VALIDATION PARAMETERS *********/
54
- /// Returns an object that contains the general options for setting up the panel.
55
- app.get('/config/options/general', (req, res) => {
56
- let opts = {
57
- countries: sys.board.valueMaps.countries.toArray(),
58
- tempUnits: sys.board.valueMaps.tempUnits.toArray(),
59
- timeZones: sys.board.valueMaps.timeZones.toArray(),
60
- clockSources: sys.board.valueMaps.clockSources.toArray(),
61
- clockModes: sys.board.valueMaps.clockModes.toArray(),
62
- pool: sys.general.get(true),
63
- sensors: sys.board.system.getSensors(),
64
- systemUnits: sys.board.valueMaps.systemUnits.toArray()
65
- };
66
- return res.status(200).send(opts);
67
- });
68
- app.get('/config/options/rs485', async (req, res, next) => {
69
- try {
70
- let opts = { ports: [], local: [], screenlogic: {} }
71
- let cfg = config.getSection('controller');
72
- for (let section in cfg) {
73
- if (section.startsWith('comms')) {
74
- let cport = extend(true, { enabled: false, netConnect: false, mock: false }, cfg[section]);
75
- let port = conn.findPortById(cport.portId || 0);
76
- if (typeof cport.type === 'undefined'){
77
- cport.type = cport.netConnect ? 'netConnect' : cport.mock ? 'mock' : 'local'
78
- }
79
- if (typeof port !== 'undefined') cport.stats = port.stats;
80
- if (port.portId === 0 && port.type === 'screenlogic') {
81
- cport.screenlogic.stats = sl.stats;
82
- }
83
- opts.ports.push(cport);
84
- }
85
- // if (section.startsWith('screenlogic')){
86
- // let screenlogic = cfg[section];
87
- // screenlogic.types = [{ val: 'local', name: 'Local', desc: 'Local Screenlogic' }, { val: 'remote', name: 'Remote', desc: 'Remote Screenlogic' }];
88
- // screenlogic.stats = sl.stats;
89
- // opts.screenlogic = screenlogic;
90
- // }
91
- }
92
- opts.local = await conn.getLocalPortsAsync() || [];
93
- return res.status(200).send(opts);
94
- } catch (err) { next(err); }
95
- });
96
- // app.get('/config/options/screenlogic', async (req, res, next) => {
97
- // try {
98
- // let cfg = config.getSection('controller.screenlogic');
99
- // let data = {
100
- // cfg,
101
- // types: [{ val: 'local', name: 'Local', desc: 'Local Screenlogic' }, { val: 'remote', name: 'Remote', desc: 'Remote Screenlogic' }]
102
- // }
103
- // return res.status(200).send(data);
104
- // } catch (err) { next(err); }
105
- // });
106
- app.get('/config/options/screenlogic/search', async (req, res, next) => {
107
- try {
108
- let localUnits = await ScreenLogicComms.searchAsync();
109
- return res.status(200).send(localUnits);
110
- } catch (err) { next(err); }
111
- });
112
- app.get('/config/options/circuits', async (req, res, next) => {
113
- try {
114
- let opts = {
115
- maxCircuits: sys.equipment.maxCircuits,
116
- equipmentIds: sys.equipment.equipmentIds.circuits,
117
- invalidIds: sys.board.equipmentIds.invalidIds.get(),
118
- equipmentNames: sys.board.circuits.getCircuitNames(),
119
- functions: sys.board.circuits.getCircuitFunctions(),
120
- circuits: sys.circuits.get(),
121
- controllerType: sys.controllerType,
122
- servers: await sys.ncp.getREMServers()
123
- };
124
- return res.status(200).send(opts);
125
- } catch (err) { next(err); }
126
- });
127
- app.get('/config/options/circuitGroups', (req, res) => {
128
- let opts = {
129
- maxCircuitGroups: sys.equipment.maxCircuitGroups,
130
- equipmentNames: sys.board.circuits.getCircuitNames(),
131
- circuits: sys.board.circuits.getCircuitReferences(true, true, false),
132
- circuitGroups: sys.circuitGroups.get(),
133
- circuitStates: sys.board.valueMaps.groupCircuitStates.toArray()
134
- };
135
- return res.status(200).send(opts);
136
- });
137
- app.get('/config/options/lightGroups', (req, res) => {
138
- let opts = {
139
- maxLightGroups: sys.equipment.maxLightGroups,
140
- equipmentNames: sys.board.circuits.getCircuitNames(),
141
- themes: sys.board.circuits.getLightThemes(),
142
- colors: sys.board.valueMaps.lightColors.toArray(),
143
- circuits: sys.board.circuits.getLightReferences(),
144
- lightGroups: sys.lightGroups.get(),
145
- functions: sys.board.circuits.getCircuitFunctions()
146
- };
147
- return res.status(200).send(opts);
148
- });
149
- app.get('/config/options/features', (req, res) => {
150
- let opts = {
151
- maxFeatures: sys.equipment.maxFeatures,
152
- invalidIds: sys.board.equipmentIds.invalidIds.get(),
153
- equipmentIds: sys.equipment.equipmentIds.features,
154
- equipmentNames: sys.board.circuits.getCircuitNames(),
155
- functions: sys.board.features.getFeatureFunctions(),
156
- features: sys.features.get()
157
- };
158
- return res.status(200).send(opts);
159
- });
160
- app.get('/config/options/bodies', (req, res) => {
161
- let opts = {
162
- maxBodies: sys.equipment.maxBodies,
163
- bodyTypes: sys.board.valueMaps.bodies.toArray(),
164
- bodies: sys.bodies.get(),
165
- capacityUnits: sys.board.valueMaps.volumeUnits.toArray()
166
- };
167
- return res.status(200).send(opts);
168
- });
169
- app.get('/config/options/valves', async (req, res, next) => {
170
- try {
171
- let opts = {
172
- maxValves: sys.equipment.maxValves,
173
- valveTypes: sys.board.valueMaps.valveTypes.toArray(),
174
- circuits: sys.board.circuits.getCircuitReferences(true, true, true),
175
- valves: sys.valves.get(),
176
- servers: await sys.ncp.getREMServers()
177
- };
178
- opts.circuits.unshift({ id: 256, name: 'Unassigned', type: 0, equipmentType: 'circuit' });
179
- return res.status(200).send(opts);
180
- } catch (err) { next(err); }
181
- });
182
- app.get('/config/options/pumps', async (req, res, next) => {
183
- try {
184
- let opts: any = {
185
- maxPumps: sys.equipment.maxPumps,
186
- pumpUnits: sys.board.valueMaps.pumpUnits.toArray(),
187
- pumpTypes: sys.board.valueMaps.pumpTypes.toArray(),
188
- models: {
189
- ss: sys.board.valueMaps.pumpSSModels.toArray(),
190
- ds: sys.board.valueMaps.pumpDSModels.toArray(),
191
- vs: sys.board.valueMaps.pumpVSModels.toArray(),
192
- vf: sys.board.valueMaps.pumpVSModels.toArray(),
193
- vsf: sys.board.valueMaps.pumpVSFModels.toArray(),
194
- vssvrs: sys.board.valueMaps.pumpVSSVRSModels.toArray()
195
- },
196
- circuits: sys.board.circuits.getCircuitReferences(true, true, true, true),
197
- bodies: sys.board.valueMaps.pumpBodies.toArray(),
198
- pumps: sys.pumps.get(),
199
- servers: await sys.ncp.getREMServers(),
200
- rs485ports: await conn.listInstalledPorts()
201
- };
202
- // RKS: Why do we need the circuit names? We have the circuits. Is this so
203
- // that we can name the pump. I thought that *Touch uses the pump type as the name
204
- // plus a number.
205
- // RG: because I need the name/val of the Not Used circuit for displaying pumpCircuits that are
206
- // empty. EG A pump circuit can be not used even if all the circuits are used.
207
- if (sys.controllerType !== ControllerType.IntelliCenter) {
208
- opts.circuitNames = sys.board.circuits.getCircuitNames().filter(c => c.name === 'notused');
209
- }
210
- return res.status(200).send(opts);
211
- } catch (err) { next(err); }
212
- });
213
- app.get('/config/options/schedules', async (req, res, next) => {
214
- try {
215
- let opts = {
216
- maxSchedules: sys.equipment.maxSchedules,
217
- tempUnits: sys.board.valueMaps.tempUnits.transform(state.temps.units),
218
- scheduleTimeTypes: sys.board.valueMaps.scheduleTimeTypes.toArray(),
219
- scheduleTypes: sys.board.valueMaps.scheduleTypes.toArray(),
220
- scheduleDays: sys.board.valueMaps.scheduleDays.toArray(),
221
- heatSources: sys.board.valueMaps.heatSources.toArray(),
222
- circuits: sys.board.circuits.getCircuitReferences(true, true, false, true),
223
- schedules: sys.schedules.get(),
224
- clockMode: sys.general.options.clockMode || 12,
225
- displayTypes: sys.board.valueMaps.scheduleDisplayTypes.toArray(),
226
- bodies: [],
227
- eggTimers: sys.eggTimers.get() // needed for *Touch to not overwrite real schedules
228
- };
229
- // Now get all the body heat sources.
230
- for (let i = 0; i < sys.bodies.length; i++) {
231
- let body = sys.bodies.getItemByIndex(i);
232
- opts.bodies.push({ id: body.id, circuit: body.circuit, name: body.name, alias: body.alias, heatSources: sys.board.bodies.getHeatSources(body.id) });
233
- }
234
- return res.status(200).send(opts);
235
- } catch (err) { next(err); }
236
- });
237
- app.get('/config/options/heaters', async (req, res, next) => {
238
- try {
239
- let opts = {
240
- tempUnits: sys.board.valueMaps.tempUnits.transform(state.temps.units),
241
- bodies: sys.board.bodies.getBodyAssociations(),
242
- maxHeaters: sys.equipment.maxHeaters,
243
- heaters: sys.heaters.get(),
244
- heaterTypes: sys.board.valueMaps.heaterTypes.toArray(),
245
- heatModes: sys.board.valueMaps.heatModes.toArray(),
246
- coolDownDelay: sys.general.options.cooldownDelay,
247
- servers: [],
248
- rs485ports: await conn.listInstalledPorts()
249
- };
250
- // We only need the servers data when the controller is a Nixie controller. We don't need to
251
- // wait for this information if we are dealing with an OCP.
252
- if (sys.controllerType === ControllerType.Nixie) opts.servers = await sys.ncp.getREMServers();
253
- return res.status(200).send(opts);
254
- } catch (err) { next(err); }
255
- });
256
- app.get('/config/options/customNames', (req, res) => {
257
- let opts = {
258
- maxCustomNames: sys.equipment.maxCustomNames,
259
- customNames: sys.customNames.get()
260
- };
261
- return res.status(200).send(opts);
262
- });
263
- app.get('/config/options/chemControllers', async (req, res, next) => {
264
- try {
265
- let remServers = await sys.ncp.getREMServers();
266
- let alarms = {
267
- flow: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 1].includes(el.val)),
268
- pH: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 2, 4].includes(el.val)),
269
- orp: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 8, 16].includes(el.val)),
270
- pHTank: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 32].includes(el.val)),
271
- orpTank: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 64].includes(el.val)),
272
- probeFault: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 128].includes(el.val))
273
- }
274
- let warnings = {
275
- waterChemistry: sys.board.valueMaps.chemControllerWarnings.toArray().filter(el => [0, 1, 2].includes(el.val)),
276
- pHLockout: sys.board.valueMaps.chemControllerLimits.toArray().filter(el => [0, 1].includes(el.val)),
277
- pHDailyLimitReached: sys.board.valueMaps.chemControllerLimits.toArray().filter(el => [0, 2].includes(el.val)),
278
- orpDailyLimitReached: sys.board.valueMaps.chemControllerLimits.toArray().filter(el => [0, 4].includes(el.val)),
279
- invalidSetup: sys.board.valueMaps.chemControllerWarnings.toArray().filter(el => [0, 8].includes(el.val)),
280
- chlorinatorCommsError: sys.board.valueMaps.chemControllerWarnings.toArray().filter(el => [0, 16].includes(el.val)),
281
- }
282
- let opts = {
283
- types: sys.board.valueMaps.chemControllerTypes.toArray(),
284
- bodies: sys.board.bodies.getBodyAssociations(),
285
- tempUnits: sys.board.valueMaps.tempUnits.toArray(),
286
- status: sys.board.valueMaps.chemControllerStatus.toArray(),
287
- pumpTypes: sys.board.valueMaps.chemPumpTypes.toArray(),
288
- phSupplyTypes: sys.board.valueMaps.phSupplyTypes.toArray(),
289
- volumeUnits: sys.board.valueMaps.volumeUnits.toArray(),
290
- dosingMethods: sys.board.valueMaps.chemDosingMethods.toArray(),
291
- chlorDosingMethods: sys.board.valueMaps.chemChlorDosingMethods.toArray(),
292
- orpProbeTypes: sys.board.valueMaps.chemORPProbeTypes.toArray(),
293
- phProbeTypes: sys.board.valueMaps.chemPhProbeTypes.toArray(),
294
- flowSensorTypes: sys.board.valueMaps.flowSensorTypes.toArray(),
295
- acidTypes: sys.board.valueMaps.acidTypes.toArray(),
296
- remServers,
297
- dosingStatus: sys.board.valueMaps.chemControllerDosingStatus.toArray(),
298
- siCalcTypes: sys.board.valueMaps.siCalcTypes.toArray(),
299
- alarms,
300
- warnings,
301
- // waterFlow: sys.board.valueMaps.chemControllerWaterFlow.toArray(), // remove
302
- controllers: sys.chemControllers.get(),
303
- maxChemControllers: sys.equipment.maxChemControllers,
304
- doserTypes: sys.board.valueMaps.chemDoserTypes.toArray(),
305
- chlorinators: sys.chlorinators.get(),
306
- };
307
- return res.status(200).send(opts);
308
- }
309
- catch (err) { next(err); }
310
- });
311
- app.get('/config/options/chemDosers', async (req, res, next) => {
312
- try {
313
- let remServers = await sys.ncp.getREMServers();
314
- let opts = {
315
- bodies: sys.board.bodies.getBodyAssociations(),
316
- tempUnits: sys.board.valueMaps.tempUnits.toArray(),
317
- status: sys.board.valueMaps.chemDoserStatus.toArray(),
318
- pumpTypes: sys.board.valueMaps.chemPumpTypes.toArray(),
319
- volumeUnits: sys.board.valueMaps.volumeUnits.toArray(),
320
- flowSensorTypes: sys.board.valueMaps.flowSensorTypes.toArray(),
321
- remServers,
322
- dosingStatus: sys.board.valueMaps.chemDoserDosingStatus.toArray(),
323
- dosers: sys.chemDosers.get(),
324
- doserTypes: sys.board.valueMaps.chemDoserTypes.toArray(),
325
- maxChemDosers: sys.equipment.maxChemDosers
326
- };
327
- return res.status(200).send(opts);
328
- }
329
- catch (err) { next(err); }
330
- });
331
-
332
- app.get('/config/options/rem', async (req, res, next) => {
333
- try {
334
- let opts = {
335
- servers: await sys.ncp.getREMServers()
336
- }
337
- return res.status(200).send(opts);
338
- } catch (err) { next(err); }
339
- });
340
- app.get('/config/options/controllerType', async (req, res, next) => {
341
- try {
342
- let opts = {
343
- controllerType: sys.controllerType,
344
- type: state.controllerState,
345
- equipment: sys.equipment.get(),
346
- controllerTypes: sys.getAvailableControllerTypes()
347
- }
348
- return res.status(200).send(opts);
349
- } catch (err) { next(err); }
350
- });
351
- app.get('/config/options/anslq25ControllerType', async (req, res, next) => {
352
- try {
353
- let opts = {
354
- // controllerType: typeof sys.anslq25.controllerType === 'undefined' ? '' : sys.anslq25.controllerType,
355
- // model: typeof sys.anslq25.model === 'undefined' ? '' : sys.anslq25.model,
356
- // equipment: sys.equipment.get(),
357
- ...sys.anslq25.get(true),
358
- controllerTypes: sys.getAvailableControllerTypes(['easytouch', 'intellitouch', 'intellicenter']),
359
- rs485ports: await conn.listInstalledPorts()
360
- }
361
- return res.status(200).send(opts);
362
- } catch (err) { next(err); }
363
- });
364
- app.get('/config/options/chlorinators', async (req, res, next) => {
365
- try {
366
- let opts = {
367
- types: sys.board.valueMaps.chlorinatorType.toArray(),
368
- bodies: sys.board.bodies.getBodyAssociations(),
369
- chlorinators: sys.chlorinators.get(),
370
- maxChlorinators: sys.equipment.maxChlorinators,
371
- models: sys.board.valueMaps.chlorinatorModel.toArray(),
372
- equipmentMasters: sys.board.valueMaps.equipmentMaster.toArray(),
373
- rs485ports: await conn.listInstalledPorts()
374
- };
375
- return res.status(200).send(opts);
376
- } catch (err) { next(err); }
377
- });
378
- app.get('/config/options/dateTime', (req, res) => {
379
- let opts = {
380
- dow: sys.board.system.getDOW()
381
- }
382
- return res.status(200).send(opts);
383
- });
384
- app.get('/app/options/logger', (req, res) => {
385
- let opts = {
386
- logger: config.getSection('log')
387
- }
388
- return res.status(200).send(opts);
389
- });
390
- app.get('/app/all/', (req, res) => {
391
- let opts = config.getSection();
392
- return res.status(200).send(opts);
393
- });
394
- app.get('/config/options/tempSensors', (req, res) => {
395
- let opts = {
396
- tempUnits: sys.board.valueMaps.tempUnits.toArray(),
397
- sensors: sys.board.system.getSensors()
398
- };
399
- return res.status(200).send(opts);
400
- });
401
- app.get('/config/options/filters', async (req, res, next) => {
402
- try {
403
- let opts = {
404
- types: sys.board.valueMaps.filterTypes.toArray(),
405
- bodies: sys.board.bodies.getBodyAssociations(),
406
- filters: sys.filters.get(),
407
- areaUnits: sys.board.valueMaps.areaUnits.toArray(),
408
- pressureUnits: sys.board.valueMaps.pressureUnits.toArray(),
409
- circuits: sys.board.circuits.getCircuitReferences(true, true, true, false),
410
- servers: []
411
- };
412
- if (sys.controllerType === ControllerType.Nixie) opts.servers = await sys.ncp.getREMServers();
413
- return res.status(200).send(opts);
414
- } catch (err) { next(err); }
415
- });
416
- /******* END OF CONFIGURATION PICK LISTS/REFERENCES AND VALIDATION ***********/
417
- /******* ENDPOINTS FOR MODIFYING THE OUTDOOR CONTROL PANEL SETTINGS **********/
418
- app.put('/config/rem', async (req, res, next) => {
419
- try {
420
- // RSG: this is problematic because we now enable multiple rem type interfaces that may not be called REM.
421
- // This is now also a dupe of PUT /app/interface and should be consolidated
422
- // config.setSection('web.interfaces.rem', req.body);
423
- config.setInterface(req.body);
424
- }
425
- catch (err) { next(err); }
426
- })
427
- app.put('/config/tempSensors', async (req, res, next) => {
428
- try {
429
- await sys.board.system.setTempSensorsAsync(req.body);
430
- let opts = {
431
- tempUnits: sys.board.valueMaps.tempUnits.toArray(),
432
- sensors: sys.board.system.getSensors()
433
- };
434
- return res.status(200).send(opts);
435
- }
436
- catch (err) { next(err); }
437
- });
438
- app.put('/config/filter', async (req, res, next) => {
439
- try {
440
- let sfilter = await sys.board.filters.setFilterAsync(req.body);
441
- return res.status(200).send(sfilter.get(true));
442
- }
443
- catch (err) { next(err); }
444
- });
445
- app.put('/config/controllerType', async (req, res, next) => {
446
- try {
447
- let controller = await sys.board.setControllerType(req.body);
448
- return res.status(200).send(controller.get(true));
449
- } catch (err) { next(err); }
450
- });
451
- app.put('/config/anslq25ControllerType', async (req, res, next) => {
452
- try {
453
- // sys.anslq25ControllerType
454
- await sys.anslq25Board.setAnslq25Async(req.body);
455
- return res.status(200).send(sys.anslq25.get(true));
456
- } catch (err) { next(err); }
457
- });
458
- app.delete('/config/filter', async (req, res, next) => {
459
- try {
460
- let sfilter = await sys.board.filters.deleteFilterAsync(req.body);
461
- return res.status(200).send(sfilter.get(true));
462
- }
463
- catch (err) { next(err); }
464
- });
465
- app.put('/config/general', async (req, res, next) => {
466
- // Change the options for the pool.
467
- try {
468
- let rc = await sys.board.system.setGeneralAsync(req.body);
469
- let opts = {
470
- countries: sys.board.valueMaps.countries.toArray(),
471
- tempUnits: sys.board.valueMaps.tempUnits.toArray(),
472
- timeZones: sys.board.valueMaps.timeZones.toArray(),
473
- clockSources: sys.board.valueMaps.clockSources.toArray(),
474
- clockModes: sys.board.valueMaps.clockModes.toArray(),
475
- pool: sys.general.get(true),
476
- sensors: sys.board.system.getSensors()
477
- };
478
- return res.status(200).send(opts);
479
- }
480
- catch (err) { next(err); }
481
- });
482
-
483
- app.put('/config/valve', async (req, res, next) => {
484
- // Update a valve.
485
- try {
486
- let valve = await sys.board.valves.setValveAsync(req.body);
487
- return res.status(200).send((valve).get(true));
488
- }
489
- catch (err) { next(err); }
490
- });
491
- app.delete('/config/valve', async (req, res, next) => {
492
- // Update a valve.
493
- try {
494
- let valve = await sys.board.valves.deleteValveAsync(req.body);
495
- return res.status(200).send((valve).get(true));
496
- }
497
- catch (err) { next(err); }
498
- });
499
-
500
- app.put('/config/body', async (req, res, next) => {
501
- // Change the body attributes.
502
- try {
503
- let body = await sys.board.bodies.setBodyAsync(req.body);
504
- return res.status(200).send((body).get(true));
505
- }
506
- catch (err) { next(err); }
507
- });
508
- app.put('/config/circuit', async (req, res, next) => {
509
- // add/update a circuit
510
- try {
511
- let circuit = await sys.board.circuits.setCircuitAsync(req.body);
512
- return res.status(200).send((circuit).get(true));
513
- }
514
- catch (err) { next(err); }
515
- });
516
- app.delete('/config/circuit', async (req, res, next) => {
517
- // delete a circuit
518
- try {
519
- let circuit = await sys.board.circuits.deleteCircuitAsync(req.body);
520
- return res.status(200).send((circuit).get(true));
521
- }
522
- catch (err) { next(err); }
523
- });
524
- app.put('/config/feature', async (req, res, next) => {
525
- // add/update a feature
526
- try {
527
- let feature = await sys.board.features.setFeatureAsync(req.body);
528
- return res.status(200).send((feature).get(true));
529
- }
530
- catch (err) { next(err); }
531
- });
532
- app.delete('/config/feature', async (req, res, next) => {
533
- // delete a feature
534
- try {
535
- let feature = await sys.board.features.deleteFeatureAsync(req.body);
536
- return res.status(200).send((feature).get(true));
537
- }
538
- catch (err) { next(err); }
539
- });
540
- app.put('/config/circuitGroup', async (req, res, next) => {
541
- // add/update a circuitGroup
542
- try {
543
- let group = await sys.board.circuits.setCircuitGroupAsync(req.body);
544
- return res.status(200).send((group).get(true));
545
- }
546
- catch (err) { next(err); }
547
- });
548
- app.delete('/config/circuitGroup', async (req, res, next) => {
549
- try {
550
- let group = await sys.board.circuits.deleteCircuitGroupAsync(req.body);
551
- return res.status(200).send((group).get(true));
552
- }
553
- catch (err) { next(err); }
554
- });
555
- app.put('/config/lightGroup', async (req, res, next) => {
556
- try {
557
- let group = await sys.board.circuits.setLightGroupAsync(req.body);
558
- return res.status(200).send((group).get(true));
559
- }
560
- catch (err) { next(err); }
561
- });
562
- app.delete('/config/lightGroup', async (req, res, next) => {
563
- try {
564
- let group = await sys.board.circuits.deleteLightGroupAsync(req.body);
565
- return res.status(200).send((group).get(true));
566
- }
567
- catch (err) { next(err); }
568
- });
569
- app.put('/config/pump', async (req, res, next) => {
570
- // Change the pump attributes. This will add the pump if it doesn't exist, set
571
- // any affiliated circuits and maintain all attribututes of the pump.
572
- // RSG: Caveat - you have to send none or all of the pump circuits or any not included be deleted.
573
- try {
574
- let pump = await sys.board.pumps.setPumpAsync(req.body);
575
- return res.status(200).send((pump).get(true));
576
- }
577
- catch (err) { next(err); }
578
- });
579
- app.put('/config/pumpCircuit', async (req, res, next) => {
580
- try {
581
- let pmpId = parseInt(req.body.pumpId, 10);
582
- let circId = parseInt(req.body.circuitId, 10);
583
- let pmp: Pump;
584
- if (isNaN(pmpId)) {
585
- let pmpAddress = parseInt(req.body.address, 10);
586
- if (!isNaN(pmpAddress)) pmp = sys.pumps.find(x => x.address === pmpAddress);
587
- }
588
- else
589
- pmp = sys.pumps.find(x => x.id === pmpId);
590
- if (typeof pmp === 'undefined') throw new ServiceProcessError(`Pump not found`, '/config/pumpCircuit', 'Set circuit speed');
591
- let data = pmp.get(true);
592
- let c = typeof data.circuits !== 'undefined' && typeof data.circuits.find !== 'undefined' ? data.circuits.find(x => x.circuit === circId) : undefined;
593
- if (typeof c === 'undefined') throw new ServiceProcessError(`Circuit not found`, '/config/pumpCircuit', 'Set circuit speed');
594
- if (typeof req.body.speed !== 'undefined') {
595
- let speed = parseInt(req.body.speed, 10);
596
- if (isNaN(speed)) throw new ServiceProcessError(`Invalid circuit speed supplied`, '/config/pumpCircuit', 'Set circuit speed');
597
- c.speed = speed;
598
- }
599
- else if (typeof req.body.flow !== 'undefined') {
600
- let flow = parseInt(req.body.flow, 10);
601
- if (isNaN(flow)) throw new ServiceProcessError(`Invalid circuit flow supplied`, '/config/pumpCircuit', 'Set circuit flow');
602
- c.flow = flow;
603
- }
604
- else {
605
- throw new ServiceProcessError(`You must supply a target flow or speed`, '/config/pumpCircuit', 'Set circuit flow');
606
- }
607
- await sys.board.pumps.setPumpAsync(data);
608
- return res.status(200).send((pmp).get(true));
609
- } catch (err) { next(err); }
610
-
611
- });
612
- // RKS: 05-20-22 This is a remnant of the old web ui. It is not called and the setType method needed to go away.
613
- //app.delete('/config/pump/:pumpId', async (req, res, next) => {
614
- // try {
615
- // let pump = sys.pumps.getItemById(parseInt(req.params.pumpId, 10));
616
- // await sys.board.pumps.deletePumpAsync()
617
- // if (pump.type === 0) {
618
- // return res.status(500).send(`Pump ${pump.id} not active`);
619
- // }
620
- // pump.setType(0);
621
- // return res.status(200).send('OK');
622
- // } catch (err) { next(err); }
623
- //});
624
- app.delete('/config/pump', async (req, res, next) => {
625
- try {
626
- let pump = await sys.board.pumps.deletePumpAsync(req.body);
627
- return res.status(200).send((pump).get(true));
628
- }
629
- catch (err) { next(err); }
630
- });
631
- app.put('/config/customNames', async (req, res, next) => {
632
- try {
633
- let names = await sys.board.system.setCustomNamesAsync(req.body);
634
- return res.status(200).send(names.get());
635
- }
636
- catch (err) { next(err); }
637
- });
638
- app.put('/config/customName', async (req, res, next) => {
639
- try {
640
- let name = await sys.board.system.setCustomNameAsync(req.body);
641
- return res.status(200).send(name.get(true));
642
- }
643
- catch (err) { next(err); }
644
- });
645
- app.get('/config/schedule/:id', (req, res) => {
646
- let schedId = parseInt(req.params.id || '0', 10);
647
- let sched = sys.schedules.getItemById(schedId).get(true);
648
- return res.status(200).send(sched);
649
- });
650
- app.put('/config/schedule', async (req, res, next) => {
651
- try {
652
- let sched = await sys.board.schedules.setScheduleAsync(req.body);
653
- return res.status(200).send((sched as Schedule).get(true));
654
- }
655
- catch (err) { next(err); }
656
- });
657
- app.delete('/config/schedule', async (req, res, next) => {
658
- try {
659
- let sched = await sys.board.schedules.deleteScheduleAsync(req.body);
660
- return res.status(200).send((sched as Schedule).get(true));
661
- }
662
- catch (err) {
663
- //console.log(`Error deleting schedule... ${err}`);
664
- next(err);
665
- }
666
- });
667
- app.put('/config/chlorinator', async (req, res, next) => {
668
- try {
669
- let chlor = await sys.board.chlorinator.setChlorAsync(req.body);
670
- return res.status(200).send(sys.chlorinators.getItemById(chlor.id).get(true));
671
- }
672
- catch (err) { next(err); }
673
- });
674
- app.delete('/config/chlorinator', async (req, res, next) => {
675
- try {
676
- let chlor = await sys.board.chlorinator.deleteChlorAsync(req.body);
677
- return res.status(200).send(chlor.get(true));
678
- }
679
- catch (err) { next(err); }
680
- });
681
- app.put('/config/heater', async (req, res, next) => {
682
- try {
683
- let heater = await sys.board.heaters.setHeaterAsync(req.body);
684
- return res.status(200).send(sys.heaters.getItemById(heater.id).get(true));
685
- }
686
- catch (err) { next(err); }
687
- });
688
- app.delete('/config/heater', async (req, res, next) => {
689
- try {
690
- let heater = await sys.board.heaters.deleteHeaterAsync(req.body);
691
- return res.status(200).send((heater as Heater).get(true));
692
- }
693
- catch (err) { next(err); }
694
- });
695
-
696
- /***** END OF ENDPOINTS FOR MODIFYINC THE OUTDOOR CONTROL PANEL SETTINGS *****/
697
-
698
-
699
-
700
- app.get('/config/circuits/names', (req, res) => {
701
- let circuitNames = sys.board.circuits.getCircuitNames();
702
- return res.status(200).send(circuitNames);
703
- });
704
- app.get('/config/circuit/functions', (req, res) => {
705
- let circuitFunctions = sys.board.circuits.getCircuitFunctions();
706
- return res.status(200).send(circuitFunctions);
707
- });
708
- app.get('/config/features/functions', (req, res) => {
709
- let featureFunctions = sys.board.features.getFeatureFunctions();
710
- return res.status(200).send(featureFunctions);
711
- });
712
- app.get('/config/circuit/:id', (req, res) => {
713
- // todo: need getInterfaceById.get() in case features are requested here
714
- // todo: it seems to make sense to combine with /state/circuit/:id as they both have similiar/overlapping info
715
- return res.status(200).send(sys.circuits.getItemById(parseInt(req.params.id, 10)).get());
716
- });
717
- app.get('/config/circuit/:id/lightThemes', (req, res) => {
718
- let circuit = sys.circuits.getInterfaceById(parseInt(req.params.id, 10));
719
- let themes = typeof circuit !== 'undefined' && typeof circuit.getLightThemes === 'function' ? circuit.getLightThemes(circuit.type) : [];
720
- return res.status(200).send(themes);
721
- });
722
- app.get('/config/circuit/:id/lightCommands', (req, res) => {
723
- let circuit = sys.circuits.getInterfaceById(parseInt(req.params.id, 10));
724
- let commands = typeof circuit !== 'undefined' && typeof circuit.getLightThemes === 'function' ? circuit.getLightCommands(circuit.type) : [];
725
- return res.status(200).send(commands);
726
- });
727
-
728
- app.get('/config/chlorinator/:id', (req, res) => {
729
- return res.status(200).send(sys.chlorinators.getItemById(parseInt(req.params.id, 10)).get());
730
- });
731
- app.get('/config/chlorinators/search', async (req, res, next) => {
732
- // Change the options for the pool.
733
- try {
734
- //await sys.board.virtualChlorinatorController.search();
735
- return res.status(200).send(sys.chlorinators.getItemById(1).get());
736
- }
737
- catch (err) {
738
- next(err);
739
- }
740
- });
741
- app.put('/config/dateTime', async (req, res, next) => {
742
- try {
743
- let time = await sys.updateControllerDateTimeAsync(req.body);
744
- return res.status(200).send(time);
745
- }
746
- catch (err) { next(err); }
747
- });
748
- app.get('/config/lightGroups/themes', (req, res) => {
749
- // RSG: is this and /config/circuit/:id/lightThemes both needed?
750
- let grp = sys.lightGroups.getItemById(parseInt(req.body.id, 10));
751
- return res.status(200).send(grp.getLightThemes());
752
- });
753
- app.get('/config/lightGroups/commands', (req, res) => {
754
- let grp = sys.lightGroups.getItemById(parseInt(req.body.id, 10));
755
- return res.status(200).send(grp.getLightCommands());
756
- });
757
- app.get('/config/lightGroup/:id', (req, res) => {
758
- // if (sys.controllerType === ControllerType.IntelliCenter) {
759
- let grp = sys.lightGroups.getItemById(parseInt(req.params.id, 10));
760
- return res.status(200).send(grp.getExtended());
761
- // }
762
- // else
763
- // return res.status(200).send(sys.intellibrite.getExtended());
764
- });
765
- app.get('/config/lightGroup/colors', (req, res) => {
766
- return res.status(200).send(sys.board.valueMaps.lightColors.toArray());
767
- });
768
- app.put('/config/lightGroup/:id/setColors', async (req, res, next) => {
769
- try {
770
- let id = parseInt(req.params.id, 10);
771
- let grp = extend(true, { id: id }, req.body);
772
- await sys.board.circuits.setLightGroupAttribsAsync(grp);
773
- return res.status(200).send(sys.lightGroups.getItemById(id).getExtended());
774
- }
775
- catch (err) { next(err); }
776
- });
777
- app.get('/config/intellibrite/themes', (req, res) => {
778
- return res.status(200).send(sys.board.circuits.getLightThemes(16));
779
- });
780
- app.get('/config/circuitGroup/:id', (req, res) => {
781
- let grp = sys.circuitGroups.getItemById(parseInt(req.params.id, 10));
782
- return res.status(200).send(grp.getExtended());
783
- });
784
- /* app.get('/config/chemController/search', async (req, res, next) => {
785
- // Change the options for the pool.
786
- try {
787
- let result = await sys.board.virtualChemControllers.search();
788
- return res.status(200).send(result);
789
- }
790
- catch (err) {
791
- next(err);
792
- }
793
- }); */
794
- app.put('/config/chemController', async (req, res, next) => {
795
- try {
796
- let chem = await sys.board.chemControllers.setChemControllerAsync(req.body);
797
- return res.status(200).send(chem.get());
798
- }
799
- catch (err) { next(err); }
800
- });
801
- app.put('/config/chemDoser', async (req, res, next) => {
802
- try {
803
- let doser = await sys.board.chemDosers.setChemDoserAsync(req.body);
804
- return res.status(200).send(doser.get());
805
- }
806
- catch (err) { next(err); }
807
-
808
- });
809
- app.put('/config/chemController/calibrateDose', async (req, res, next) => {
810
- try {
811
- let schem = await sys.board.chemControllers.calibrateDoseAsync(req.body);
812
- return res.status(200).send(schem.getExtended());
813
- }
814
- catch (err) { next(err); }
815
- });
816
- app.put('/config/chemDoser/calibrateDose', async (req, res, next) => {
817
- try {
818
- let schem = await sys.board.chemDosers.calibrateDoseAsync(req.body);
819
- return res.status(200).send(schem.getExtended());
820
- }
821
- catch (err) { next(err); }
822
- });
823
- app.put('/config/chemController/feed', async (req, res, next) => {
824
- try {
825
- let chem = await sys.board.chemControllers.setChemControllerAsync(req.body);
826
- return res.status(200).send(chem.get());
827
- }
828
- catch (err) { next(err); }
829
- });
830
- app.delete('/config/chemController', async (req, res, next) => {
831
- try {
832
- let chem = await sys.board.chemControllers.deleteChemControllerAsync(req.body);
833
- return res.status(200).send(chem.get());
834
- }
835
- catch (err) { next(err); }
836
- });
837
- app.delete('/config/chemDoser', async (req, res, next) => {
838
- try {
839
- let doser = await sys.board.chemDosers.deleteChemDoserAsync(req.body);
840
- return res.status(200).send(doser.get());
841
- }
842
- catch (err) { next(err); }
843
-
844
- });
845
-
846
- /* app.get('/config/intellibrite', (req, res) => {
847
- return res.status(200).send(sys.intellibrite.getExtended());
848
- });
849
- app.get('/config/intellibrite/colors', (req, res) => {
850
- return res.status(200).send(sys.board.valueMaps.lightColors.toArray());
851
- });
852
- app.put('/config/intellibrite/setColors', (req, res) => {
853
- let grp = extend(true, { id: 0 }, req.body);
854
- sys.board.circuits.setIntelliBriteColors(new LightGroup(grp));
855
- return res.status(200).send('OK');
856
- }); */
857
- app.get('/config', (req, res) => {
858
- return res.status(200).send(sys.getSection('all'));
859
- });
860
- app.get('/config/:section', (req, res) => {
861
- return res.status(200).send(sys.getSection(req.params.section));
862
- });
863
-
864
-
865
- /******* ENDPOINTS FOR MANAGING THE poolController APPLICATION *********/
866
- app.put('/app/logger/setOptions', (req, res) => {
867
- logger.setOptions(req.body);
868
- return res.status(200).send(logger.options);
869
- });
870
- app.put('/app/logger/clearMessages', (req, res) => {
871
- logger.clearMessages();
872
- return res.status(200).send('OK');
873
- });
874
- app.get('/app/messages/broadcast/actions', (req, res) => {
875
- return res.status(200).send(sys.board.valueMaps.msgBroadcastActions.toArray());
876
- });
877
- app.put('/app/config/reload', (req, res) => {
878
- sys.board.reloadConfig();
879
- return res.status(200).send('OK');
880
- });
881
- app.put('/app/interface', async (req, res, next) => {
882
- try {
883
- let iface = await webApp.updateServerInterface(req.body);
884
- return res.status(200).send(iface);
885
- }
886
- catch (err) { next(err); }
887
- });
888
- app.put('/app/rs485Port', async (req, res, next) => {
889
- try {
890
- let port = await conn.setPortAsync(req.body);
891
- return res.status(200).send(port);
892
- }
893
- catch (err) { next(err); }
894
- });
895
- // app.put('/app/screenlogic', async (req, res, next) => {
896
- // try {
897
- // let screenlogic = await sl.setScreenlogicAsync(req.body);
898
- // return res.status(200).send(screenlogic);
899
- // }
900
- // catch (err) { next(err); }
901
- // });
902
- app.delete('/app/rs485Port', async (req, res, next) => {
903
- try {
904
- let port = await conn.deleteAuxPort(req.body);
905
- return res.status(200).send(port);
906
- }
907
- catch (err) { next(err); }
908
- });
909
- app.get('/app/config/startPacketCapture', (req, res) => {
910
- startPacketCapture(true);
911
- return res.status(200).send('OK');
912
- });
913
- app.get('/app/config/startPacketCaptureWithoutReset', (req, res) => {
914
- startPacketCapture(false);
915
- return res.status(200).send('OK');
916
- });
917
- app.get('/app/config/stopPacketCapture', async (req, res, next) => {
918
- try {
919
- let file = await stopPacketCaptureAsync();
920
- res.download(file);
921
- }
922
- catch (err) { next(err); }
923
- });
924
- app.get('/app/config/:section', (req, res) => {
925
- return res.status(200).send(config.getSection(req.params.section));
926
- });
927
- app.get('/app/config/options/backup', async (req, res, next) => {
928
- try {
929
- let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0 }, keepCount: 5, servers: [] });
930
- let servers = await sys.ncp.getREMServers();
931
- if (typeof servers !== 'undefined') {
932
- // Just in case somebody deletes the backup section and doesn't put it back properly.
933
- for (let i = 0; i < servers.length; i++) {
934
- let srv = servers[i];
935
- 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 });
936
- }
937
- for (let i = opts.servers.length - 1; i >= 0; i--) {
938
- let srv = opts.servers[i];
939
- if (typeof servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.splice(i, 1);
940
- }
941
- }
942
- if (typeof opts.servers === 'undefined') opts.servers = [];
943
- return res.status(200).send(opts);
944
- } catch (err) { next(err); }
945
- });
946
- app.get('/app/config/options/restore', async (req, res, next) => {
947
- try {
948
- let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0 }, keepCount: 5, servers: [], backupFiles: [] });
949
- let servers = await sys.ncp.getREMServers();
950
- if (typeof servers !== 'undefined') {
951
- for (let i = 0; i < servers.length; i++) {
952
- let srv = servers[i];
953
- if (typeof opts.servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.push({ name: srv.name, uuid: srv.uuid, backup: false });
954
- }
955
- for (let i = opts.servers.length - 1; i >= 0; i--) {
956
- let srv = opts.servers[i];
957
- if (typeof servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.splice(i, 1);
958
- }
959
- }
960
- if (typeof opts.servers === 'undefined') opts.servers = [];
961
- opts.backupFiles = await webApp.readBackupFiles();
962
- return res.status(200).send(opts);
963
- } catch (err) { next(err); }
964
-
965
- });
966
- app.put('/app/config/options/backup', async (req, res, next) => {
967
- try {
968
- config.setSection('controller.backups', req.body);
969
- let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0 }, keepCount: 5, servers: [] });
970
- webApp.autoBackup = utils.makeBool(opts.automatic);
971
- await webApp.checkAutoBackup();
972
- return res.status(200).send(opts);
973
- } catch (err) { next(err); }
974
-
975
- });
976
- app.put('/app/config/createBackup', async (req, res, next) => {
977
- try {
978
- let ret = await webApp.backupServer(req.body);
979
- res.download(ret.filePath);
980
- }
981
- catch (err) { next(err); }
982
- });
983
- app.delete('/app/backup/file', async (req, res, next) => {
984
- try {
985
- let opts = req.body;
986
- fs.unlinkSync(opts.filePath);
987
- return res.status(200).send(opts);
988
- }
989
- catch (err) { next(err); }
990
- });
991
- app.post('/app/backup/file', async (req, res, next) => {
992
- try {
993
- let file = multer({
994
- limits: { fileSize: 1000000 },
995
- storage: multer.memoryStorage()
996
- }).single('backupFile');
997
- file(req, res, async (err) => {
998
- try {
999
- if (err) { next(err); }
1000
- else {
1001
- // Validate the incoming data and save it off only if it is valid.
1002
- let bf = await BackupFile.fromBuffer(req.file.originalname, req.file.buffer);
1003
- if (typeof bf === 'undefined') {
1004
- err = new ServiceProcessError(`Invalid backup file: ${req.file.originalname}`, 'POST: app/backup/file', 'extractBackupOptions');
1005
- next(err);
1006
- }
1007
- else {
1008
- if (fs.existsSync(bf.filePath))
1009
- return next(new ServiceProcessError(`File already exists ${req.file.originalname}`, 'POST: app/backup/file', 'writeFile'));
1010
- else {
1011
- try {
1012
- fs.writeFileSync(bf.filePath, new Uint8Array(req.file.buffer));
1013
- } catch (e) { logger.error(`Error writing backup file ${e.message}`); }
1014
- }
1015
- return res.status(200).send(bf);
1016
- }
1017
- }
1018
- } catch (e) {
1019
- err = new ServiceProcessError(`Error uploading file: ${e.message}`, 'POST: app/backup/file', 'uploadFile');
1020
- next(err);
1021
- logger.error(`Error uploading file ${e.message}`);
1022
- }
1023
- });
1024
- } catch (err) { next(err); }
1025
- });
1026
- app.put('/app/restore/validate', async (req, res, next) => {
1027
- try {
1028
- // Validate all the restore options.
1029
- let opts = req.body;
1030
- let ctx = await webApp.validateRestore(opts);
1031
- return res.status(200).send(ctx);
1032
- } catch (err) { next(err); }
1033
- });
1034
- app.put('/app/restore/file', async (req, res, next) => {
1035
- try {
1036
- let opts = req.body;
1037
- let results = await webApp.restoreServers(opts);
1038
- return res.status(200).send(results);
1039
- } catch (err) { next(err); }
1040
- });
1041
- app.put('/app/anslq25', async(req, res, next) => {
1042
- try {
1043
- await sys.anslq25Board.setAnslq25Async(req.body);
1044
- return res.status(200).send(sys.anslq25.get(true));
1045
- } catch (err) { next(err); }
1046
- });
1047
- app.delete('/app/anslq25', async(req, res, next) => {
1048
- try {
1049
- await sys.anslq25Board.deleteAnslq25Async(req.body);
1050
- return res.status(200).send(sys.anslq25.get(true));
1051
- } catch (err) { next(err); }
1052
- });
1053
- }
1
+ /* nodejs-poolController. An application to control pool equipment.
2
+ Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
3
+ Russell Goldin, tagyoureit. russ.goldin@gmail.com
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU Affero General Public License as
7
+ published by the Free Software Foundation, either version 3 of the
8
+ License, or (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU Affero General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Affero General Public License
16
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ import * as fs from "fs";
19
+ import * as path from "path";
20
+ import * as express from "express";
21
+ import * as extend from 'extend';
22
+ import * as multer from 'multer';
23
+ import { sys, LightGroup, ControllerType, Pump, Valve, Body, General, Circuit, ICircuit, Feature, CircuitGroup, CustomNameCollection, Schedule, Chlorinator, Heater, Screenlogic } from "../../../controller/Equipment";
24
+ import { config } from "../../../config/Config";
25
+ import { logger } from "../../../logger/Logger";
26
+ import { utils } from "../../../controller/Constants";
27
+ import { ServiceProcessError } from "../../../controller/Errors";
28
+ import { state } from "../../../controller/State";
29
+ import { stopPacketCaptureAsync, startPacketCapture } from '../../../app';
30
+ import { conn } from "../../../controller/comms/Comms";
31
+ import { webApp, BackupFile, RestoreFile } from "../../Server";
32
+ import { release } from "os";
33
+ import { ScreenLogicComms, sl } from "../../../controller/comms/ScreenLogic";
34
+ import { screenlogic } from "node-screenlogic";
35
+
36
+ export class ConfigRoute {
37
+ public static initRoutes(app: express.Application) {
38
+ app.get('/config/body/:body/heatModes', (req, res) => {
39
+ return res.status(200).send(sys.bodies.getItemById(parseInt(req.params.body, 10)).getHeatModes());
40
+ });
41
+ app.get('/config/circuit/names', (req, res) => {
42
+ let circuitNames = sys.board.circuits.getCircuitNames();
43
+ return res.status(200).send(circuitNames);
44
+ });
45
+ app.get('/config/circuit/references', (req, res) => {
46
+ let circuits = typeof req.query.circuits === 'undefined' || utils.makeBool(req.query.circuits);
47
+ let features = typeof req.query.features === 'undefined' || utils.makeBool(req.query.features);
48
+ let groups = typeof req.query.features === 'undefined' || utils.makeBool(req.query.groups);
49
+ let virtual = typeof req.query.virtual === 'undefined' || utils.makeBool(req.query.virtual);
50
+ return res.status(200).send(sys.board.circuits.getCircuitReferences(circuits, features, virtual, groups));
51
+ });
52
+
53
+ /******* CONFIGURATION PICK LISTS/REFERENCES and VALIDATION PARAMETERS *********/
54
+ /// Returns an object that contains the general options for setting up the panel.
55
+ app.get('/config/options/general', (req, res) => {
56
+ let opts = {
57
+ countries: sys.board.valueMaps.countries.toArray(),
58
+ tempUnits: sys.board.valueMaps.tempUnits.toArray(),
59
+ timeZones: sys.board.valueMaps.timeZones.toArray(),
60
+ clockSources: sys.board.valueMaps.clockSources.toArray(),
61
+ clockModes: sys.board.valueMaps.clockModes.toArray(),
62
+ pool: sys.general.get(true),
63
+ sensors: sys.board.system.getSensors(),
64
+ systemUnits: sys.board.valueMaps.systemUnits.toArray()
65
+ };
66
+ return res.status(200).send(opts);
67
+ });
68
+ app.get('/config/options/rs485', async (req, res, next) => {
69
+ try {
70
+ let opts = { ports: [], local: [], screenlogic: {} }
71
+ let cfg = config.getSection('controller');
72
+ for (let section in cfg) {
73
+ if (section.startsWith('comms')) {
74
+ let cport = extend(true, { enabled: false, netConnect: false, mock: false }, cfg[section]);
75
+ let port = conn.findPortById(cport.portId || 0);
76
+ if (typeof cport.type === 'undefined'){
77
+ cport.type = cport.netConnect ? 'netConnect' : cport.mock ? 'mock' : 'local'
78
+ }
79
+ if (typeof port !== 'undefined') cport.stats = port.stats;
80
+ if (port.portId === 0 && port.type === 'screenlogic') {
81
+ cport.screenlogic.stats = sl.stats;
82
+ }
83
+ opts.ports.push(cport);
84
+ }
85
+ // if (section.startsWith('screenlogic')){
86
+ // let screenlogic = cfg[section];
87
+ // screenlogic.types = [{ val: 'local', name: 'Local', desc: 'Local Screenlogic' }, { val: 'remote', name: 'Remote', desc: 'Remote Screenlogic' }];
88
+ // screenlogic.stats = sl.stats;
89
+ // opts.screenlogic = screenlogic;
90
+ // }
91
+ }
92
+ opts.local = await conn.getLocalPortsAsync() || [];
93
+ return res.status(200).send(opts);
94
+ } catch (err) { next(err); }
95
+ });
96
+ // app.get('/config/options/screenlogic', async (req, res, next) => {
97
+ // try {
98
+ // let cfg = config.getSection('controller.screenlogic');
99
+ // let data = {
100
+ // cfg,
101
+ // types: [{ val: 'local', name: 'Local', desc: 'Local Screenlogic' }, { val: 'remote', name: 'Remote', desc: 'Remote Screenlogic' }]
102
+ // }
103
+ // return res.status(200).send(data);
104
+ // } catch (err) { next(err); }
105
+ // });
106
+ app.get('/config/options/screenlogic/search', async (req, res, next) => {
107
+ try {
108
+ let localUnits = await ScreenLogicComms.searchAsync();
109
+ return res.status(200).send(localUnits);
110
+ } catch (err) { next(err); }
111
+ });
112
+ app.get('/config/options/circuits', async (req, res, next) => {
113
+ try {
114
+ let opts = {
115
+ maxCircuits: sys.equipment.maxCircuits,
116
+ equipmentIds: sys.equipment.equipmentIds.circuits,
117
+ invalidIds: sys.board.equipmentIds.invalidIds.get(),
118
+ equipmentNames: sys.board.circuits.getCircuitNames(),
119
+ functions: sys.board.circuits.getCircuitFunctions(),
120
+ circuits: sys.circuits.get(),
121
+ controllerType: sys.controllerType,
122
+ servers: await sys.ncp.getREMServers()
123
+ };
124
+ return res.status(200).send(opts);
125
+ } catch (err) { next(err); }
126
+ });
127
+ app.get('/config/options/circuitGroups', (req, res) => {
128
+ let opts = {
129
+ maxCircuitGroups: sys.equipment.maxCircuitGroups,
130
+ equipmentNames: sys.board.circuits.getCircuitNames(),
131
+ circuits: sys.board.circuits.getCircuitReferences(true, true, false),
132
+ circuitGroups: sys.circuitGroups.get(),
133
+ circuitStates: sys.board.valueMaps.groupCircuitStates.toArray()
134
+ };
135
+ return res.status(200).send(opts);
136
+ });
137
+ app.get('/config/options/lightGroups', (req, res) => {
138
+ let opts = {
139
+ maxLightGroups: sys.equipment.maxLightGroups,
140
+ equipmentNames: sys.board.circuits.getCircuitNames(),
141
+ themes: sys.board.circuits.getLightThemes(),
142
+ colors: sys.board.valueMaps.lightColors.toArray(),
143
+ circuits: sys.board.circuits.getLightReferences(),
144
+ lightGroups: sys.lightGroups.get(),
145
+ functions: sys.board.circuits.getCircuitFunctions()
146
+ };
147
+ return res.status(200).send(opts);
148
+ });
149
+ app.get('/config/options/features', (req, res) => {
150
+ let opts = {
151
+ maxFeatures: sys.equipment.maxFeatures,
152
+ invalidIds: sys.board.equipmentIds.invalidIds.get(),
153
+ equipmentIds: sys.equipment.equipmentIds.features,
154
+ equipmentNames: sys.board.circuits.getCircuitNames(),
155
+ functions: sys.board.features.getFeatureFunctions(),
156
+ features: sys.features.get()
157
+ };
158
+ return res.status(200).send(opts);
159
+ });
160
+ app.get('/config/options/bodies', (req, res) => {
161
+ let opts = {
162
+ maxBodies: sys.equipment.maxBodies,
163
+ bodyTypes: sys.board.valueMaps.bodies.toArray(),
164
+ bodies: sys.bodies.get(),
165
+ capacityUnits: sys.board.valueMaps.volumeUnits.toArray()
166
+ };
167
+ return res.status(200).send(opts);
168
+ });
169
+ app.get('/config/options/valves', async (req, res, next) => {
170
+ try {
171
+ let opts = {
172
+ maxValves: sys.equipment.maxValves,
173
+ valveTypes: sys.board.valueMaps.valveTypes.toArray(),
174
+ circuits: sys.board.circuits.getCircuitReferences(true, true, true),
175
+ valves: sys.valves.get(),
176
+ servers: await sys.ncp.getREMServers()
177
+ };
178
+ opts.circuits.unshift({ id: 256, name: 'Unassigned', type: 0, equipmentType: 'circuit' });
179
+ return res.status(200).send(opts);
180
+ } catch (err) { next(err); }
181
+ });
182
+ app.get('/config/options/pumps', async (req, res, next) => {
183
+ try {
184
+ let opts: any = {
185
+ maxPumps: sys.equipment.maxPumps,
186
+ pumpUnits: sys.board.valueMaps.pumpUnits.toArray(),
187
+ pumpTypes: sys.board.valueMaps.pumpTypes.toArray(),
188
+ models: {
189
+ ss: sys.board.valueMaps.pumpSSModels.toArray(),
190
+ ds: sys.board.valueMaps.pumpDSModels.toArray(),
191
+ vs: sys.board.valueMaps.pumpVSModels.toArray(),
192
+ vf: sys.board.valueMaps.pumpVSModels.toArray(),
193
+ vsf: sys.board.valueMaps.pumpVSFModels.toArray(),
194
+ vssvrs: sys.board.valueMaps.pumpVSSVRSModels.toArray()
195
+ },
196
+ circuits: sys.board.circuits.getCircuitReferences(true, true, true, true),
197
+ bodies: sys.board.valueMaps.pumpBodies.toArray(),
198
+ pumps: sys.pumps.get(),
199
+ servers: await sys.ncp.getREMServers(),
200
+ rs485ports: await conn.listInstalledPorts()
201
+ };
202
+ // RKS: Why do we need the circuit names? We have the circuits. Is this so
203
+ // that we can name the pump. I thought that *Touch uses the pump type as the name
204
+ // plus a number.
205
+ // RG: because I need the name/val of the Not Used circuit for displaying pumpCircuits that are
206
+ // empty. EG A pump circuit can be not used even if all the circuits are used.
207
+ if (sys.controllerType !== ControllerType.IntelliCenter) {
208
+ opts.circuitNames = sys.board.circuits.getCircuitNames().filter(c => c.name === 'notused');
209
+ }
210
+ return res.status(200).send(opts);
211
+ } catch (err) { next(err); }
212
+ });
213
+ app.get('/config/options/schedules', async (req, res, next) => {
214
+ try {
215
+ let opts = {
216
+ maxSchedules: sys.equipment.maxSchedules,
217
+ tempUnits: sys.board.valueMaps.tempUnits.transform(state.temps.units),
218
+ scheduleTimeTypes: sys.board.valueMaps.scheduleTimeTypes.toArray(),
219
+ scheduleTypes: sys.board.valueMaps.scheduleTypes.toArray(),
220
+ scheduleDays: sys.board.valueMaps.scheduleDays.toArray(),
221
+ heatSources: sys.board.valueMaps.heatSources.toArray(),
222
+ circuits: sys.board.circuits.getCircuitReferences(true, true, false, true),
223
+ schedules: sys.schedules.get(),
224
+ clockMode: sys.general.options.clockMode || 12,
225
+ displayTypes: sys.board.valueMaps.scheduleDisplayTypes.toArray(),
226
+ bodies: [],
227
+ eggTimers: sys.eggTimers.get() // needed for *Touch to not overwrite real schedules
228
+ };
229
+ // Now get all the body heat sources.
230
+ for (let i = 0; i < sys.bodies.length; i++) {
231
+ let body = sys.bodies.getItemByIndex(i);
232
+ opts.bodies.push({ id: body.id, circuit: body.circuit, name: body.name, alias: body.alias, heatSources: sys.board.bodies.getHeatSources(body.id) });
233
+ }
234
+ return res.status(200).send(opts);
235
+ } catch (err) { next(err); }
236
+ });
237
+ app.get('/config/options/heaters', async (req, res, next) => {
238
+ try {
239
+ // Ensure heat mode/source valueMaps reflect the *current board* before returning picklists.
240
+ // Without this, startup can expose generic defaults until the first status/config packets arrive.
241
+ sys.board.heaters.updateHeaterServices();
242
+ let opts = {
243
+ tempUnits: sys.board.valueMaps.tempUnits.transform(state.temps.units),
244
+ bodies: sys.board.bodies.getBodyAssociations(),
245
+ maxHeaters: sys.equipment.maxHeaters,
246
+ heaters: sys.heaters.get(),
247
+ heaterTypes: sys.board.valueMaps.heaterTypes.toArray(),
248
+ // Align with `/config/body/:id/heatModes` (body picklist). This ensures any board-specific
249
+ // filtering (e.g. IntelliCenter v3 preferred-mode suppression) is reflected consistently.
250
+ // Future improvement should return valid modes per body.
251
+ heatModes: sys.board.bodies.getHeatModes(1),
252
+ coolDownDelay: sys.general.options.cooldownDelay,
253
+ servers: [],
254
+ rs485ports: await conn.listInstalledPorts()
255
+ };
256
+ // We only need the servers data when the controller is a Nixie controller. We don't need to
257
+ // wait for this information if we are dealing with an OCP.
258
+ if (sys.controllerType === ControllerType.Nixie) opts.servers = await sys.ncp.getREMServers();
259
+ return res.status(200).send(opts);
260
+ } catch (err) { next(err); }
261
+ });
262
+ app.get('/config/options/customNames', (req, res) => {
263
+ let opts = {
264
+ maxCustomNames: sys.equipment.maxCustomNames,
265
+ customNames: sys.customNames.get()
266
+ };
267
+ return res.status(200).send(opts);
268
+ });
269
+ app.get('/config/options/chemControllers', async (req, res, next) => {
270
+ try {
271
+ let remServers = await sys.ncp.getREMServers();
272
+ let alarms = {
273
+ flow: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 1].includes(el.val)),
274
+ pH: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 2, 4].includes(el.val)),
275
+ orp: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 8, 16].includes(el.val)),
276
+ pHTank: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 32].includes(el.val)),
277
+ orpTank: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 64].includes(el.val)),
278
+ probeFault: sys.board.valueMaps.chemControllerAlarms.toArray().filter(el => [0, 128].includes(el.val))
279
+ }
280
+ let warnings = {
281
+ waterChemistry: sys.board.valueMaps.chemControllerWarnings.toArray().filter(el => [0, 1, 2].includes(el.val)),
282
+ pHLockout: sys.board.valueMaps.chemControllerLimits.toArray().filter(el => [0, 1].includes(el.val)),
283
+ pHDailyLimitReached: sys.board.valueMaps.chemControllerLimits.toArray().filter(el => [0, 2].includes(el.val)),
284
+ orpDailyLimitReached: sys.board.valueMaps.chemControllerLimits.toArray().filter(el => [0, 4].includes(el.val)),
285
+ invalidSetup: sys.board.valueMaps.chemControllerWarnings.toArray().filter(el => [0, 8].includes(el.val)),
286
+ chlorinatorCommsError: sys.board.valueMaps.chemControllerWarnings.toArray().filter(el => [0, 16].includes(el.val)),
287
+ }
288
+ let opts = {
289
+ types: sys.board.valueMaps.chemControllerTypes.toArray(),
290
+ bodies: sys.board.bodies.getBodyAssociations(),
291
+ tempUnits: sys.board.valueMaps.tempUnits.toArray(),
292
+ status: sys.board.valueMaps.chemControllerStatus.toArray(),
293
+ pumpTypes: sys.board.valueMaps.chemPumpTypes.toArray(),
294
+ phSupplyTypes: sys.board.valueMaps.phSupplyTypes.toArray(),
295
+ volumeUnits: sys.board.valueMaps.volumeUnits.toArray(),
296
+ dosingMethods: sys.board.valueMaps.chemDosingMethods.toArray(),
297
+ chlorDosingMethods: sys.board.valueMaps.chemChlorDosingMethods.toArray(),
298
+ orpProbeTypes: sys.board.valueMaps.chemORPProbeTypes.toArray(),
299
+ phProbeTypes: sys.board.valueMaps.chemPhProbeTypes.toArray(),
300
+ flowSensorTypes: sys.board.valueMaps.flowSensorTypes.toArray(),
301
+ acidTypes: sys.board.valueMaps.acidTypes.toArray(),
302
+ remServers,
303
+ dosingStatus: sys.board.valueMaps.chemControllerDosingStatus.toArray(),
304
+ siCalcTypes: sys.board.valueMaps.siCalcTypes.toArray(),
305
+ alarms,
306
+ warnings,
307
+ // waterFlow: sys.board.valueMaps.chemControllerWaterFlow.toArray(), // remove
308
+ controllers: sys.chemControllers.get(),
309
+ maxChemControllers: sys.equipment.maxChemControllers,
310
+ doserTypes: sys.board.valueMaps.chemDoserTypes.toArray(),
311
+ chlorinators: sys.chlorinators.get(),
312
+ };
313
+ return res.status(200).send(opts);
314
+ }
315
+ catch (err) { next(err); }
316
+ });
317
+ app.get('/config/options/chemDosers', async (req, res, next) => {
318
+ try {
319
+ let remServers = await sys.ncp.getREMServers();
320
+ let opts = {
321
+ bodies: sys.board.bodies.getBodyAssociations(),
322
+ tempUnits: sys.board.valueMaps.tempUnits.toArray(),
323
+ status: sys.board.valueMaps.chemDoserStatus.toArray(),
324
+ pumpTypes: sys.board.valueMaps.chemPumpTypes.toArray(),
325
+ volumeUnits: sys.board.valueMaps.volumeUnits.toArray(),
326
+ flowSensorTypes: sys.board.valueMaps.flowSensorTypes.toArray(),
327
+ remServers,
328
+ dosingStatus: sys.board.valueMaps.chemDoserDosingStatus.toArray(),
329
+ dosers: sys.chemDosers.get(),
330
+ doserTypes: sys.board.valueMaps.chemDoserTypes.toArray(),
331
+ maxChemDosers: sys.equipment.maxChemDosers
332
+ };
333
+ return res.status(200).send(opts);
334
+ }
335
+ catch (err) { next(err); }
336
+ });
337
+
338
+ app.get('/config/options/rem', async (req, res, next) => {
339
+ try {
340
+ let opts = {
341
+ servers: await sys.ncp.getREMServers()
342
+ }
343
+ return res.status(200).send(opts);
344
+ } catch (err) { next(err); }
345
+ });
346
+ app.get('/config/options/controllerType', async (req, res, next) => {
347
+ try {
348
+ let opts = {
349
+ controllerType: sys.controllerType,
350
+ type: state.controllerState,
351
+ equipment: sys.equipment.get(),
352
+ controllerTypes: sys.getAvailableControllerTypes()
353
+ }
354
+ return res.status(200).send(opts);
355
+ } catch (err) { next(err); }
356
+ });
357
+ app.get('/config/options/anslq25ControllerType', async (req, res, next) => {
358
+ try {
359
+ let opts = {
360
+ // controllerType: typeof sys.anslq25.controllerType === 'undefined' ? '' : sys.anslq25.controllerType,
361
+ // model: typeof sys.anslq25.model === 'undefined' ? '' : sys.anslq25.model,
362
+ // equipment: sys.equipment.get(),
363
+ ...sys.anslq25.get(true),
364
+ controllerTypes: sys.getAvailableControllerTypes(['easytouch', 'intellitouch', 'intellicenter']),
365
+ rs485ports: await conn.listInstalledPorts()
366
+ }
367
+ return res.status(200).send(opts);
368
+ } catch (err) { next(err); }
369
+ });
370
+ app.get('/config/options/chlorinators', async (req, res, next) => {
371
+ try {
372
+ let opts = {
373
+ types: sys.board.valueMaps.chlorinatorType.toArray(),
374
+ bodies: sys.board.bodies.getBodyAssociations(),
375
+ chlorinators: sys.chlorinators.get(),
376
+ maxChlorinators: sys.equipment.maxChlorinators,
377
+ models: sys.board.valueMaps.chlorinatorModel.toArray(),
378
+ equipmentMasters: sys.board.valueMaps.equipmentMaster.toArray(),
379
+ rs485ports: await conn.listInstalledPorts()
380
+ };
381
+ return res.status(200).send(opts);
382
+ } catch (err) { next(err); }
383
+ });
384
+ app.get('/config/options/dateTime', (req, res) => {
385
+ let opts = {
386
+ dow: sys.board.system.getDOW()
387
+ }
388
+ return res.status(200).send(opts);
389
+ });
390
+ app.get('/app/options/logger', (req, res) => {
391
+ let opts = {
392
+ logger: config.getSection('log')
393
+ }
394
+ return res.status(200).send(opts);
395
+ });
396
+ app.get('/app/all/', (req, res) => {
397
+ let opts = config.getSection();
398
+ return res.status(200).send(opts);
399
+ });
400
+ app.get('/config/options/tempSensors', (req, res) => {
401
+ let opts = {
402
+ tempUnits: sys.board.valueMaps.tempUnits.toArray(),
403
+ sensors: sys.board.system.getSensors()
404
+ };
405
+ return res.status(200).send(opts);
406
+ });
407
+ app.get('/config/options/filters', async (req, res, next) => {
408
+ try {
409
+ let opts = {
410
+ types: sys.board.valueMaps.filterTypes.toArray(),
411
+ bodies: sys.board.bodies.getBodyAssociations(),
412
+ filters: sys.filters.get(),
413
+ areaUnits: sys.board.valueMaps.areaUnits.toArray(),
414
+ pressureUnits: sys.board.valueMaps.pressureUnits.toArray(),
415
+ circuits: sys.board.circuits.getCircuitReferences(true, true, true, false),
416
+ servers: []
417
+ };
418
+ if (sys.controllerType === ControllerType.Nixie) opts.servers = await sys.ncp.getREMServers();
419
+ return res.status(200).send(opts);
420
+ } catch (err) { next(err); }
421
+ });
422
+ /******* END OF CONFIGURATION PICK LISTS/REFERENCES AND VALIDATION ***********/
423
+ /******* ENDPOINTS FOR MODIFYING THE OUTDOOR CONTROL PANEL SETTINGS **********/
424
+ app.put('/config/rem', async (req, res, next) => {
425
+ try {
426
+ // RSG: this is problematic because we now enable multiple rem type interfaces that may not be called REM.
427
+ // This is now also a dupe of PUT /app/interface and should be consolidated
428
+ // config.setSection('web.interfaces.rem', req.body);
429
+ config.setInterface(req.body);
430
+ }
431
+ catch (err) { next(err); }
432
+ })
433
+ app.put('/config/tempSensors', async (req, res, next) => {
434
+ try {
435
+ await sys.board.system.setTempSensorsAsync(req.body);
436
+ let opts = {
437
+ tempUnits: sys.board.valueMaps.tempUnits.toArray(),
438
+ sensors: sys.board.system.getSensors()
439
+ };
440
+ return res.status(200).send(opts);
441
+ }
442
+ catch (err) { next(err); }
443
+ });
444
+ app.put('/config/filter', async (req, res, next) => {
445
+ try {
446
+ let sfilter = await sys.board.filters.setFilterAsync(req.body);
447
+ return res.status(200).send(sfilter.get(true));
448
+ }
449
+ catch (err) { next(err); }
450
+ });
451
+ app.put('/config/controllerType', async (req, res, next) => {
452
+ try {
453
+ let controller = await sys.board.setControllerType(req.body);
454
+ return res.status(200).send(controller.get(true));
455
+ } catch (err) { next(err); }
456
+ });
457
+ app.put('/config/anslq25ControllerType', async (req, res, next) => {
458
+ try {
459
+ // sys.anslq25ControllerType
460
+ await sys.anslq25Board.setAnslq25Async(req.body);
461
+ return res.status(200).send(sys.anslq25.get(true));
462
+ } catch (err) { next(err); }
463
+ });
464
+ app.delete('/config/filter', async (req, res, next) => {
465
+ try {
466
+ let sfilter = await sys.board.filters.deleteFilterAsync(req.body);
467
+ return res.status(200).send(sfilter.get(true));
468
+ }
469
+ catch (err) { next(err); }
470
+ });
471
+ app.put('/config/general', async (req, res, next) => {
472
+ // Change the options for the pool.
473
+ try {
474
+ let rc = await sys.board.system.setGeneralAsync(req.body);
475
+ let opts = {
476
+ countries: sys.board.valueMaps.countries.toArray(),
477
+ tempUnits: sys.board.valueMaps.tempUnits.toArray(),
478
+ timeZones: sys.board.valueMaps.timeZones.toArray(),
479
+ clockSources: sys.board.valueMaps.clockSources.toArray(),
480
+ clockModes: sys.board.valueMaps.clockModes.toArray(),
481
+ pool: sys.general.get(true),
482
+ sensors: sys.board.system.getSensors()
483
+ };
484
+ return res.status(200).send(opts);
485
+ }
486
+ catch (err) { next(err); }
487
+ });
488
+
489
+ app.put('/config/valve', async (req, res, next) => {
490
+ // Update a valve.
491
+ try {
492
+ let valve = await sys.board.valves.setValveAsync(req.body);
493
+ return res.status(200).send((valve).get(true));
494
+ }
495
+ catch (err) { next(err); }
496
+ });
497
+ app.delete('/config/valve', async (req, res, next) => {
498
+ // Update a valve.
499
+ try {
500
+ let valve = await sys.board.valves.deleteValveAsync(req.body);
501
+ return res.status(200).send((valve).get(true));
502
+ }
503
+ catch (err) { next(err); }
504
+ });
505
+
506
+ app.put('/config/body', async (req, res, next) => {
507
+ // Change the body attributes.
508
+ try {
509
+ let body = await sys.board.bodies.setBodyAsync(req.body);
510
+ return res.status(200).send((body).get(true));
511
+ }
512
+ catch (err) { next(err); }
513
+ });
514
+ app.put('/config/circuit', async (req, res, next) => {
515
+ // add/update a circuit
516
+ try {
517
+ let circuit = await sys.board.circuits.setCircuitAsync(req.body);
518
+ return res.status(200).send((circuit).get(true));
519
+ }
520
+ catch (err) { next(err); }
521
+ });
522
+ app.delete('/config/circuit', async (req, res, next) => {
523
+ // delete a circuit
524
+ try {
525
+ let circuit = await sys.board.circuits.deleteCircuitAsync(req.body);
526
+ return res.status(200).send((circuit).get(true));
527
+ }
528
+ catch (err) { next(err); }
529
+ });
530
+ app.put('/config/feature', async (req, res, next) => {
531
+ // add/update a feature
532
+ try {
533
+ let feature = await sys.board.features.setFeatureAsync(req.body);
534
+ return res.status(200).send((feature).get(true));
535
+ }
536
+ catch (err) { next(err); }
537
+ });
538
+ app.delete('/config/feature', async (req, res, next) => {
539
+ // delete a feature
540
+ try {
541
+ let feature = await sys.board.features.deleteFeatureAsync(req.body);
542
+ return res.status(200).send((feature).get(true));
543
+ }
544
+ catch (err) { next(err); }
545
+ });
546
+ app.put('/config/circuitGroup', async (req, res, next) => {
547
+ // add/update a circuitGroup
548
+ try {
549
+ let group = await sys.board.circuits.setCircuitGroupAsync(req.body);
550
+ return res.status(200).send((group).get(true));
551
+ }
552
+ catch (err) { next(err); }
553
+ });
554
+ app.delete('/config/circuitGroup', async (req, res, next) => {
555
+ try {
556
+ let group = await sys.board.circuits.deleteCircuitGroupAsync(req.body);
557
+ return res.status(200).send((group).get(true));
558
+ }
559
+ catch (err) { next(err); }
560
+ });
561
+ app.put('/config/lightGroup', async (req, res, next) => {
562
+ try {
563
+ let group = await sys.board.circuits.setLightGroupAsync(req.body);
564
+ return res.status(200).send((group).get(true));
565
+ }
566
+ catch (err) { next(err); }
567
+ });
568
+ app.delete('/config/lightGroup', async (req, res, next) => {
569
+ try {
570
+ let group = await sys.board.circuits.deleteLightGroupAsync(req.body);
571
+ return res.status(200).send((group).get(true));
572
+ }
573
+ catch (err) { next(err); }
574
+ });
575
+ app.put('/config/pump', async (req, res, next) => {
576
+ // Change the pump attributes. This will add the pump if it doesn't exist, set
577
+ // any affiliated circuits and maintain all attribututes of the pump.
578
+ // RSG: Caveat - you have to send none or all of the pump circuits or any not included be deleted.
579
+ try {
580
+ let pump = await sys.board.pumps.setPumpAsync(req.body);
581
+ return res.status(200).send((pump).get(true));
582
+ }
583
+ catch (err) { next(err); }
584
+ });
585
+ app.put('/config/pumpCircuit', async (req, res, next) => {
586
+ try {
587
+ let pmpId = parseInt(req.body.pumpId, 10);
588
+ let circId = parseInt(req.body.circuitId, 10);
589
+ let pmp: Pump;
590
+ if (isNaN(pmpId)) {
591
+ let pmpAddress = parseInt(req.body.address, 10);
592
+ if (!isNaN(pmpAddress)) pmp = sys.pumps.find(x => x.address === pmpAddress);
593
+ }
594
+ else
595
+ pmp = sys.pumps.find(x => x.id === pmpId);
596
+ if (typeof pmp === 'undefined') throw new ServiceProcessError(`Pump not found`, '/config/pumpCircuit', 'Set circuit speed');
597
+ let data = pmp.get(true);
598
+ let c = typeof data.circuits !== 'undefined' && typeof data.circuits.find !== 'undefined' ? data.circuits.find(x => x.circuit === circId) : undefined;
599
+ if (typeof c === 'undefined') throw new ServiceProcessError(`Circuit not found`, '/config/pumpCircuit', 'Set circuit speed');
600
+ if (typeof req.body.speed !== 'undefined') {
601
+ let speed = parseInt(req.body.speed, 10);
602
+ if (isNaN(speed)) throw new ServiceProcessError(`Invalid circuit speed supplied`, '/config/pumpCircuit', 'Set circuit speed');
603
+ c.speed = speed;
604
+ }
605
+ else if (typeof req.body.flow !== 'undefined') {
606
+ let flow = parseInt(req.body.flow, 10);
607
+ if (isNaN(flow)) throw new ServiceProcessError(`Invalid circuit flow supplied`, '/config/pumpCircuit', 'Set circuit flow');
608
+ c.flow = flow;
609
+ }
610
+ else {
611
+ throw new ServiceProcessError(`You must supply a target flow or speed`, '/config/pumpCircuit', 'Set circuit flow');
612
+ }
613
+ await sys.board.pumps.setPumpAsync(data);
614
+ return res.status(200).send((pmp).get(true));
615
+ } catch (err) { next(err); }
616
+
617
+ });
618
+ // RKS: 05-20-22 This is a remnant of the old web ui. It is not called and the setType method needed to go away.
619
+ //app.delete('/config/pump/:pumpId', async (req, res, next) => {
620
+ // try {
621
+ // let pump = sys.pumps.getItemById(parseInt(req.params.pumpId, 10));
622
+ // await sys.board.pumps.deletePumpAsync()
623
+ // if (pump.type === 0) {
624
+ // return res.status(500).send(`Pump ${pump.id} not active`);
625
+ // }
626
+ // pump.setType(0);
627
+ // return res.status(200).send('OK');
628
+ // } catch (err) { next(err); }
629
+ //});
630
+ app.delete('/config/pump', async (req, res, next) => {
631
+ try {
632
+ let pump = await sys.board.pumps.deletePumpAsync(req.body);
633
+ return res.status(200).send((pump).get(true));
634
+ }
635
+ catch (err) { next(err); }
636
+ });
637
+ app.put('/config/customNames', async (req, res, next) => {
638
+ try {
639
+ let names = await sys.board.system.setCustomNamesAsync(req.body);
640
+ return res.status(200).send(names.get());
641
+ }
642
+ catch (err) { next(err); }
643
+ });
644
+ app.put('/config/customName', async (req, res, next) => {
645
+ try {
646
+ let name = await sys.board.system.setCustomNameAsync(req.body);
647
+ return res.status(200).send(name.get(true));
648
+ }
649
+ catch (err) { next(err); }
650
+ });
651
+ app.get('/config/schedule/:id', (req, res) => {
652
+ let schedId = parseInt(req.params.id || '0', 10);
653
+ let sched = sys.schedules.getItemById(schedId).get(true);
654
+ return res.status(200).send(sched);
655
+ });
656
+ app.put('/config/schedule', async (req, res, next) => {
657
+ try {
658
+ let sched = await sys.board.schedules.setScheduleAsync(req.body);
659
+ return res.status(200).send((sched as Schedule).get(true));
660
+ }
661
+ catch (err) { next(err); }
662
+ });
663
+ app.delete('/config/schedule', async (req, res, next) => {
664
+ try {
665
+ let sched = await sys.board.schedules.deleteScheduleAsync(req.body);
666
+ return res.status(200).send((sched as Schedule).get(true));
667
+ }
668
+ catch (err) {
669
+ //console.log(`Error deleting schedule... ${err}`);
670
+ next(err);
671
+ }
672
+ });
673
+ app.put('/config/chlorinator', async (req, res, next) => {
674
+ try {
675
+ let chlor = await sys.board.chlorinator.setChlorAsync(req.body);
676
+ return res.status(200).send(sys.chlorinators.getItemById(chlor.id).get(true));
677
+ }
678
+ catch (err) { next(err); }
679
+ });
680
+ app.delete('/config/chlorinator', async (req, res, next) => {
681
+ try {
682
+ let chlor = await sys.board.chlorinator.deleteChlorAsync(req.body);
683
+ return res.status(200).send(chlor.get(true));
684
+ }
685
+ catch (err) { next(err); }
686
+ });
687
+ app.put('/config/heater', async (req, res, next) => {
688
+ try {
689
+ let heater = await sys.board.heaters.setHeaterAsync(req.body);
690
+ return res.status(200).send(sys.heaters.getItemById(heater.id).get(true));
691
+ }
692
+ catch (err) { next(err); }
693
+ });
694
+ app.delete('/config/heater', async (req, res, next) => {
695
+ try {
696
+ let heater = await sys.board.heaters.deleteHeaterAsync(req.body);
697
+ return res.status(200).send((heater as Heater).get(true));
698
+ }
699
+ catch (err) { next(err); }
700
+ });
701
+
702
+ /***** END OF ENDPOINTS FOR MODIFYINC THE OUTDOOR CONTROL PANEL SETTINGS *****/
703
+
704
+
705
+
706
+ app.get('/config/circuits/names', (req, res) => {
707
+ let circuitNames = sys.board.circuits.getCircuitNames();
708
+ return res.status(200).send(circuitNames);
709
+ });
710
+ app.get('/config/circuit/functions', (req, res) => {
711
+ let circuitFunctions = sys.board.circuits.getCircuitFunctions();
712
+ return res.status(200).send(circuitFunctions);
713
+ });
714
+ app.get('/config/features/functions', (req, res) => {
715
+ let featureFunctions = sys.board.features.getFeatureFunctions();
716
+ return res.status(200).send(featureFunctions);
717
+ });
718
+ app.get('/config/circuit/:id', (req, res) => {
719
+ // todo: need getInterfaceById.get() in case features are requested here
720
+ // todo: it seems to make sense to combine with /state/circuit/:id as they both have similiar/overlapping info
721
+ return res.status(200).send(sys.circuits.getItemById(parseInt(req.params.id, 10)).get());
722
+ });
723
+ app.get('/config/circuit/:id/lightThemes', (req, res) => {
724
+ let circuit = sys.circuits.getInterfaceById(parseInt(req.params.id, 10));
725
+ let themes = typeof circuit !== 'undefined' && typeof circuit.getLightThemes === 'function' ? circuit.getLightThemes(circuit.type) : [];
726
+ return res.status(200).send(themes);
727
+ });
728
+ app.get('/config/circuit/:id/lightCommands', (req, res) => {
729
+ let circuit = sys.circuits.getInterfaceById(parseInt(req.params.id, 10));
730
+ let commands = typeof circuit !== 'undefined' && typeof circuit.getLightThemes === 'function' ? circuit.getLightCommands(circuit.type) : [];
731
+ return res.status(200).send(commands);
732
+ });
733
+
734
+ app.get('/config/chlorinator/:id', (req, res) => {
735
+ return res.status(200).send(sys.chlorinators.getItemById(parseInt(req.params.id, 10)).get());
736
+ });
737
+ app.get('/config/chlorinators/search', async (req, res, next) => {
738
+ // Change the options for the pool.
739
+ try {
740
+ //await sys.board.virtualChlorinatorController.search();
741
+ return res.status(200).send(sys.chlorinators.getItemById(1).get());
742
+ }
743
+ catch (err) {
744
+ next(err);
745
+ }
746
+ });
747
+ app.put('/config/dateTime', async (req, res, next) => {
748
+ try {
749
+ let time = await sys.updateControllerDateTimeAsync(req.body);
750
+ return res.status(200).send(time);
751
+ }
752
+ catch (err) { next(err); }
753
+ });
754
+ app.get('/config/lightGroups/themes', (req, res) => {
755
+ // RSG: is this and /config/circuit/:id/lightThemes both needed?
756
+ let grp = sys.lightGroups.getItemById(parseInt(req.body.id, 10));
757
+ return res.status(200).send(grp.getLightThemes());
758
+ });
759
+ app.get('/config/lightGroups/commands', (req, res) => {
760
+ let grp = sys.lightGroups.getItemById(parseInt(req.body.id, 10));
761
+ return res.status(200).send(grp.getLightCommands());
762
+ });
763
+ app.get('/config/lightGroup/:id', (req, res) => {
764
+ // if (sys.controllerType === ControllerType.IntelliCenter) {
765
+ let grp = sys.lightGroups.getItemById(parseInt(req.params.id, 10));
766
+ return res.status(200).send(grp.getExtended());
767
+ // }
768
+ // else
769
+ // return res.status(200).send(sys.intellibrite.getExtended());
770
+ });
771
+ app.get('/config/lightGroup/colors', (req, res) => {
772
+ return res.status(200).send(sys.board.valueMaps.lightColors.toArray());
773
+ });
774
+ app.put('/config/lightGroup/:id/setColors', async (req, res, next) => {
775
+ try {
776
+ let id = parseInt(req.params.id, 10);
777
+ let grp = extend(true, { id: id }, req.body);
778
+ await sys.board.circuits.setLightGroupAttribsAsync(grp);
779
+ return res.status(200).send(sys.lightGroups.getItemById(id).getExtended());
780
+ }
781
+ catch (err) { next(err); }
782
+ });
783
+ app.get('/config/intellibrite/themes', (req, res) => {
784
+ return res.status(200).send(sys.board.circuits.getLightThemes(16));
785
+ });
786
+ app.get('/config/circuitGroup/:id', (req, res) => {
787
+ let grp = sys.circuitGroups.getItemById(parseInt(req.params.id, 10));
788
+ return res.status(200).send(grp.getExtended());
789
+ });
790
+ /* app.get('/config/chemController/search', async (req, res, next) => {
791
+ // Change the options for the pool.
792
+ try {
793
+ let result = await sys.board.virtualChemControllers.search();
794
+ return res.status(200).send(result);
795
+ }
796
+ catch (err) {
797
+ next(err);
798
+ }
799
+ }); */
800
+ app.put('/config/chemController', async (req, res, next) => {
801
+ try {
802
+ let chem = await sys.board.chemControllers.setChemControllerAsync(req.body);
803
+ return res.status(200).send(chem.get());
804
+ }
805
+ catch (err) { next(err); }
806
+ });
807
+ app.put('/config/chemDoser', async (req, res, next) => {
808
+ try {
809
+ let doser = await sys.board.chemDosers.setChemDoserAsync(req.body);
810
+ return res.status(200).send(doser.get());
811
+ }
812
+ catch (err) { next(err); }
813
+
814
+ });
815
+ app.put('/config/chemController/calibrateDose', async (req, res, next) => {
816
+ try {
817
+ let schem = await sys.board.chemControllers.calibrateDoseAsync(req.body);
818
+ return res.status(200).send(schem.getExtended());
819
+ }
820
+ catch (err) { next(err); }
821
+ });
822
+ app.put('/config/chemDoser/calibrateDose', async (req, res, next) => {
823
+ try {
824
+ let schem = await sys.board.chemDosers.calibrateDoseAsync(req.body);
825
+ return res.status(200).send(schem.getExtended());
826
+ }
827
+ catch (err) { next(err); }
828
+ });
829
+ app.put('/config/chemController/feed', async (req, res, next) => {
830
+ try {
831
+ let chem = await sys.board.chemControllers.setChemControllerAsync(req.body);
832
+ return res.status(200).send(chem.get());
833
+ }
834
+ catch (err) { next(err); }
835
+ });
836
+ app.delete('/config/chemController', async (req, res, next) => {
837
+ try {
838
+ let chem = await sys.board.chemControllers.deleteChemControllerAsync(req.body);
839
+ return res.status(200).send(chem.get());
840
+ }
841
+ catch (err) { next(err); }
842
+ });
843
+ app.delete('/config/chemDoser', async (req, res, next) => {
844
+ try {
845
+ let doser = await sys.board.chemDosers.deleteChemDoserAsync(req.body);
846
+ return res.status(200).send(doser.get());
847
+ }
848
+ catch (err) { next(err); }
849
+
850
+ });
851
+
852
+ /* app.get('/config/intellibrite', (req, res) => {
853
+ return res.status(200).send(sys.intellibrite.getExtended());
854
+ });
855
+ app.get('/config/intellibrite/colors', (req, res) => {
856
+ return res.status(200).send(sys.board.valueMaps.lightColors.toArray());
857
+ });
858
+ app.put('/config/intellibrite/setColors', (req, res) => {
859
+ let grp = extend(true, { id: 0 }, req.body);
860
+ sys.board.circuits.setIntelliBriteColors(new LightGroup(grp));
861
+ return res.status(200).send('OK');
862
+ }); */
863
+ app.get('/config', (req, res) => {
864
+ return res.status(200).send(sys.getSection('all'));
865
+ });
866
+ app.get('/config/:section', (req, res) => {
867
+ return res.status(200).send(sys.getSection(req.params.section));
868
+ });
869
+
870
+
871
+ /******* ENDPOINTS FOR MANAGING THE poolController APPLICATION *********/
872
+ app.put('/app/logger/setOptions', (req, res) => {
873
+ logger.setOptions(req.body);
874
+ return res.status(200).send(logger.options);
875
+ });
876
+ app.put('/app/logger/clearMessages', (req, res) => {
877
+ logger.clearMessages();
878
+ return res.status(200).send('OK');
879
+ });
880
+ app.get('/app/messages/broadcast/actions', (req, res) => {
881
+ return res.status(200).send(sys.board.valueMaps.msgBroadcastActions.toArray());
882
+ });
883
+ app.put('/app/config/reload', (req, res) => {
884
+ sys.board.reloadConfig();
885
+ return res.status(200).send('OK');
886
+ });
887
+ app.put('/app/interface', async (req, res, next) => {
888
+ try {
889
+ let iface = await webApp.updateServerInterface(req.body);
890
+ return res.status(200).send(iface);
891
+ }
892
+ catch (err) { next(err); }
893
+ });
894
+ app.put('/app/rs485Port', async (req, res, next) => {
895
+ try {
896
+ let port = await conn.setPortAsync(req.body);
897
+ return res.status(200).send(port);
898
+ }
899
+ catch (err) { next(err); }
900
+ });
901
+ // app.put('/app/screenlogic', async (req, res, next) => {
902
+ // try {
903
+ // let screenlogic = await sl.setScreenlogicAsync(req.body);
904
+ // return res.status(200).send(screenlogic);
905
+ // }
906
+ // catch (err) { next(err); }
907
+ // });
908
+ app.delete('/app/rs485Port', async (req, res, next) => {
909
+ try {
910
+ let port = await conn.deleteAuxPort(req.body);
911
+ return res.status(200).send(port);
912
+ }
913
+ catch (err) { next(err); }
914
+ });
915
+ app.get('/app/config/startPacketCapture', async (req, res, next) => {
916
+ try {
917
+ await startPacketCapture(true);
918
+ return res.status(200).send('OK');
919
+ } catch (err) { next(err); }
920
+ });
921
+ app.get('/app/config/startPacketCaptureWithoutReset', async (req, res, next) => {
922
+ try {
923
+ await startPacketCapture(false);
924
+ return res.status(200).send('OK');
925
+ } catch (err) { next(err); }
926
+ });
927
+ app.get('/app/config/stopPacketCapture', async (req, res, next) => {
928
+ try {
929
+ let file = await stopPacketCaptureAsync();
930
+ res.download(file);
931
+ }
932
+ catch (err) { next(err); }
933
+ });
934
+ app.get('/app/config/:section', (req, res) => {
935
+ return res.status(200).send(config.getSection(req.params.section));
936
+ });
937
+ app.get('/app/config/options/backup', async (req, res, next) => {
938
+ try {
939
+ let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0 }, keepCount: 5, servers: [] });
940
+ let servers = await sys.ncp.getREMServers();
941
+ if (typeof servers !== 'undefined') {
942
+ // Just in case somebody deletes the backup section and doesn't put it back properly.
943
+ for (let i = 0; i < servers.length; i++) {
944
+ let srv = servers[i];
945
+ 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 });
946
+ }
947
+ for (let i = opts.servers.length - 1; i >= 0; i--) {
948
+ let srv = opts.servers[i];
949
+ if (typeof servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.splice(i, 1);
950
+ }
951
+ }
952
+ if (typeof opts.servers === 'undefined') opts.servers = [];
953
+ return res.status(200).send(opts);
954
+ } catch (err) { next(err); }
955
+ });
956
+ app.get('/app/config/options/restore', async (req, res, next) => {
957
+ try {
958
+ let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0 }, keepCount: 5, servers: [], backupFiles: [] });
959
+ let servers = await sys.ncp.getREMServers();
960
+ if (typeof servers !== 'undefined') {
961
+ for (let i = 0; i < servers.length; i++) {
962
+ let srv = servers[i];
963
+ if (typeof opts.servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.push({ name: srv.name, uuid: srv.uuid, backup: false });
964
+ }
965
+ for (let i = opts.servers.length - 1; i >= 0; i--) {
966
+ let srv = opts.servers[i];
967
+ if (typeof servers.find(elem => elem.uuid === srv.uuid) === 'undefined') opts.servers.splice(i, 1);
968
+ }
969
+ }
970
+ if (typeof opts.servers === 'undefined') opts.servers = [];
971
+ opts.backupFiles = await webApp.readBackupFiles();
972
+ return res.status(200).send(opts);
973
+ } catch (err) { next(err); }
974
+
975
+ });
976
+ app.put('/app/config/options/backup', async (req, res, next) => {
977
+ try {
978
+ config.setSection('controller.backups', req.body);
979
+ let opts = config.getSection('controller.backups', { automatic: false, interval: { days: 30, hours: 0 }, keepCount: 5, servers: [] });
980
+ webApp.autoBackup = utils.makeBool(opts.automatic);
981
+ await webApp.checkAutoBackup();
982
+ return res.status(200).send(opts);
983
+ } catch (err) { next(err); }
984
+
985
+ });
986
+ app.put('/app/config/createBackup', async (req, res, next) => {
987
+ try {
988
+ let ret = await webApp.backupServer(req.body);
989
+ res.download(ret.filePath);
990
+ }
991
+ catch (err) { next(err); }
992
+ });
993
+ app.delete('/app/backup/file', async (req, res, next) => {
994
+ try {
995
+ let opts = req.body;
996
+ fs.unlinkSync(opts.filePath);
997
+ return res.status(200).send(opts);
998
+ }
999
+ catch (err) { next(err); }
1000
+ });
1001
+ app.post('/app/backup/file', async (req, res, next) => {
1002
+ try {
1003
+ let file = multer({
1004
+ limits: { fileSize: 1000000 },
1005
+ storage: multer.memoryStorage()
1006
+ }).single('backupFile');
1007
+ file(req, res, async (err) => {
1008
+ try {
1009
+ if (err) { next(err); }
1010
+ else {
1011
+ // Validate the incoming data and save it off only if it is valid.
1012
+ let bf = await BackupFile.fromBuffer(req.file.originalname, req.file.buffer);
1013
+ if (typeof bf === 'undefined') {
1014
+ err = new ServiceProcessError(`Invalid backup file: ${req.file.originalname}`, 'POST: app/backup/file', 'extractBackupOptions');
1015
+ next(err);
1016
+ }
1017
+ else {
1018
+ if (fs.existsSync(bf.filePath))
1019
+ return next(new ServiceProcessError(`File already exists ${req.file.originalname}`, 'POST: app/backup/file', 'writeFile'));
1020
+ else {
1021
+ try {
1022
+ fs.writeFileSync(bf.filePath, new Uint8Array(req.file.buffer));
1023
+ } catch (e) { logger.error(`Error writing backup file ${e.message}`); }
1024
+ }
1025
+ return res.status(200).send(bf);
1026
+ }
1027
+ }
1028
+ } catch (e) {
1029
+ err = new ServiceProcessError(`Error uploading file: ${e.message}`, 'POST: app/backup/file', 'uploadFile');
1030
+ next(err);
1031
+ logger.error(`Error uploading file ${e.message}`);
1032
+ }
1033
+ });
1034
+ } catch (err) { next(err); }
1035
+ });
1036
+ app.put('/app/restore/validate', async (req, res, next) => {
1037
+ try {
1038
+ // Validate all the restore options.
1039
+ let opts = req.body;
1040
+ let ctx = await webApp.validateRestore(opts);
1041
+ return res.status(200).send(ctx);
1042
+ } catch (err) { next(err); }
1043
+ });
1044
+ app.put('/app/restore/file', async (req, res, next) => {
1045
+ try {
1046
+ let opts = req.body;
1047
+ let results = await webApp.restoreServers(opts);
1048
+ return res.status(200).send(results);
1049
+ } catch (err) { next(err); }
1050
+ });
1051
+ app.put('/app/anslq25', async(req, res, next) => {
1052
+ try {
1053
+ await sys.anslq25Board.setAnslq25Async(req.body);
1054
+ return res.status(200).send(sys.anslq25.get(true));
1055
+ } catch (err) { next(err); }
1056
+ });
1057
+ app.delete('/app/anslq25', async(req, res, next) => {
1058
+ try {
1059
+ await sys.anslq25Board.deleteAnslq25Async(req.body);
1060
+ return res.status(200).send(sys.anslq25.get(true));
1061
+ } catch (err) { next(err); }
1062
+ });
1063
+ }
1054
1064
  }