nodejs-poolcontroller 7.7.0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +26 -35
- package/Changelog +22 -0
- package/README.md +7 -3
- package/anslq25/MessagesMock.ts +218 -0
- package/anslq25/boards/MockBoardFactory.ts +50 -0
- package/anslq25/boards/MockEasyTouchBoard.ts +696 -0
- package/anslq25/boards/MockSystemBoard.ts +217 -0
- package/anslq25/chemistry/MockChlorinator.ts +75 -0
- package/anslq25/pumps/MockPump.ts +84 -0
- package/app.ts +10 -14
- package/config/Config.ts +13 -9
- package/config/VersionCheck.ts +6 -2
- package/controller/Constants.ts +58 -25
- package/controller/Equipment.ts +224 -41
- package/controller/Errors.ts +2 -1
- package/controller/Lockouts.ts +34 -2
- package/controller/State.ts +491 -48
- package/controller/boards/AquaLinkBoard.ts +6 -3
- package/controller/boards/BoardFactory.ts +5 -1
- package/controller/boards/EasyTouchBoard.ts +1971 -1751
- package/controller/boards/IntelliCenterBoard.ts +1311 -1688
- package/controller/boards/IntelliComBoard.ts +7 -1
- package/controller/boards/IntelliTouchBoard.ts +153 -42
- package/controller/boards/NixieBoard.ts +209 -66
- package/controller/boards/SunTouchBoard.ts +393 -0
- package/controller/boards/SystemBoard.ts +1862 -1543
- package/controller/comms/Comms.ts +539 -138
- package/controller/comms/ScreenLogic.ts +1663 -0
- package/controller/comms/messages/Messages.ts +242 -60
- package/controller/comms/messages/config/ChlorinatorMessage.ts +4 -3
- package/controller/comms/messages/config/CircuitGroupMessage.ts +5 -2
- package/controller/comms/messages/config/CircuitMessage.ts +81 -13
- package/controller/comms/messages/config/ConfigMessage.ts +3 -1
- package/controller/comms/messages/config/CoverMessage.ts +2 -1
- package/controller/comms/messages/config/CustomNameMessage.ts +2 -1
- package/controller/comms/messages/config/EquipmentMessage.ts +5 -1
- package/controller/comms/messages/config/ExternalMessage.ts +33 -3
- package/controller/comms/messages/config/FeatureMessage.ts +2 -1
- package/controller/comms/messages/config/GeneralMessage.ts +2 -1
- package/controller/comms/messages/config/HeaterMessage.ts +3 -1
- package/controller/comms/messages/config/IntellichemMessage.ts +2 -1
- package/controller/comms/messages/config/OptionsMessage.ts +12 -6
- package/controller/comms/messages/config/PumpMessage.ts +9 -12
- package/controller/comms/messages/config/RemoteMessage.ts +80 -13
- package/controller/comms/messages/config/ScheduleMessage.ts +43 -3
- package/controller/comms/messages/config/SecurityMessage.ts +2 -1
- package/controller/comms/messages/config/ValveMessage.ts +43 -26
- package/controller/comms/messages/status/ChlorinatorStateMessage.ts +8 -7
- package/controller/comms/messages/status/EquipmentStateMessage.ts +93 -20
- package/controller/comms/messages/status/HeaterStateMessage.ts +24 -5
- package/controller/comms/messages/status/IntelliChemStateMessage.ts +7 -4
- package/controller/comms/messages/status/IntelliValveStateMessage.ts +2 -1
- package/controller/comms/messages/status/PumpStateMessage.ts +72 -4
- package/controller/comms/messages/status/VersionMessage.ts +2 -1
- package/controller/nixie/Nixie.ts +15 -4
- package/controller/nixie/NixieEquipment.ts +1 -0
- package/controller/nixie/chemistry/ChemController.ts +300 -129
- package/controller/nixie/chemistry/ChemDoser.ts +806 -0
- package/controller/nixie/chemistry/Chlorinator.ts +133 -129
- package/controller/nixie/circuits/Circuit.ts +171 -30
- package/controller/nixie/heaters/Heater.ts +337 -173
- package/controller/nixie/pumps/Pump.ts +264 -236
- package/controller/nixie/schedules/Schedule.ts +9 -3
- package/defaultConfig.json +45 -5
- package/logger/Logger.ts +38 -9
- package/package.json +13 -9
- package/web/Server.ts +235 -122
- package/web/bindings/aqualinkD.json +114 -59
- package/web/bindings/homeassistant.json +437 -0
- package/web/bindings/influxDB.json +15 -0
- package/web/bindings/mqtt.json +28 -9
- package/web/bindings/mqttAlt.json +15 -0
- package/web/interfaces/baseInterface.ts +58 -7
- package/web/interfaces/httpInterface.ts +5 -2
- package/web/interfaces/influxInterface.ts +9 -2
- package/web/interfaces/mqttInterface.ts +234 -74
- package/web/interfaces/ruleInterface.ts +87 -0
- package/web/services/config/Config.ts +140 -33
- package/web/services/config/ConfigSocket.ts +2 -1
- package/web/services/state/State.ts +144 -3
- package/web/services/state/StateSocket.ts +65 -14
- package/web/services/utilities/Utilities.ts +189 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
+
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
4
|
|
|
4
5
|
This program is free software: you can redistribute it and/or modify
|
|
5
6
|
it under the terms of the GNU Affero General Public License as
|
|
@@ -28,7 +29,7 @@ export class StateSocket {
|
|
|
28
29
|
await state.circuits.toggleCircuitStateAsync(parseInt(data.id, 10));
|
|
29
30
|
// return res.status(200).send(cstate);
|
|
30
31
|
}
|
|
31
|
-
catch (err) {logger.error(err);}
|
|
32
|
+
catch (err) {logger.error(`Socket processing error /state/circuit/toggleState: ${err.message}`);}
|
|
32
33
|
});
|
|
33
34
|
sock.on('/state/body/heatMode', async (data: any) => {
|
|
34
35
|
// RKS: 06-24-20 -- Changed this so that users can send in the body id, circuit id, or the name.
|
|
@@ -52,7 +53,8 @@ export class StateSocket {
|
|
|
52
53
|
}
|
|
53
54
|
await sys.board.bodies.setHeatModeAsync(body, mode);
|
|
54
55
|
// return res.status(200).send(tbody);
|
|
55
|
-
}
|
|
56
|
+
}
|
|
57
|
+
catch (err) { logger.error(`Socket processing error /state/body/heatmode: ${err.message}`); }
|
|
56
58
|
});
|
|
57
59
|
sock.on('/state/body/setPoint', async (data: any) => {
|
|
58
60
|
// RKS: 06-24-20 -- Changed this so that users can send in the body id, circuit id, or the name.
|
|
@@ -65,14 +67,14 @@ export class StateSocket {
|
|
|
65
67
|
}
|
|
66
68
|
await sys.board.bodies.setHeatSetpointAsync(body, parseInt(data.setPoint, 10));
|
|
67
69
|
// return res.status(200).send(tbody);
|
|
68
|
-
} catch (err) { logger.error(err); }
|
|
70
|
+
} catch (err) { logger.error(`Socket processing error /state/body/setPoint: ${err.message}`); }
|
|
69
71
|
});
|
|
70
72
|
sock.on('/temps', async (data: any) => {
|
|
71
73
|
try {
|
|
72
74
|
data = JSON.parse(data);
|
|
73
75
|
await sys.board.system.setTempsAsync(data).catch(err => logger.error(err));
|
|
74
76
|
}
|
|
75
|
-
catch (err) { logger.error(err); }
|
|
77
|
+
catch (err) { logger.error(`Socket processing error /temps: ${err.message}`); }
|
|
76
78
|
});
|
|
77
79
|
|
|
78
80
|
sock.on('/chlorinator', async (data: any) => {
|
|
@@ -98,7 +100,7 @@ export class StateSocket {
|
|
|
98
100
|
schlor.emitEquipmentChange();
|
|
99
101
|
}
|
|
100
102
|
}
|
|
101
|
-
catch (err) { logger.error(err); }
|
|
103
|
+
catch (err) { logger.error(`Socket processing error /chlorinator: ${err.message}`); }
|
|
102
104
|
});
|
|
103
105
|
sock.on('/filter', async (data: any) => {
|
|
104
106
|
try {
|
|
@@ -112,9 +114,7 @@ export class StateSocket {
|
|
|
112
114
|
await sys.board.filters.setFilterPressure(filter.id, data.pressure, data.pressureUnits || pu.name);
|
|
113
115
|
sfilter.emitEquipmentChange();
|
|
114
116
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
} catch (err) { logger.error(err); }
|
|
117
|
+
} catch (err) { logger.error(`Socket processing error /filter: ${err.message}`); }
|
|
118
118
|
});
|
|
119
119
|
sock.on('/chemController', async (data: any) => {
|
|
120
120
|
try {
|
|
@@ -160,7 +160,25 @@ export class StateSocket {
|
|
|
160
160
|
if (!isNaN(parseFloat(data.orpTank.capacity))) scontroller.orp.tank.capacity = controller.orp.tank.capacity = parseFloat(data.orpTank.capacity);
|
|
161
161
|
if (typeof data.orpTank.units === 'string') scontroller.orp.tank.units = controller.orp.tank.units = data.orpTank.units;
|
|
162
162
|
}
|
|
163
|
+
if (typeof data.acidPump !== 'undefined' || typeof data.orpPump !== 'undefined') {
|
|
164
|
+
let chem = typeof data.acidPump !== 'undefined' ? controller.ph : controller.orp;
|
|
165
|
+
let schem = typeof data.acidPump !== 'undefined' ? scontroller.ph : scontroller.orp;
|
|
166
|
+
let vals = typeof data.acidPump !== 'undefined' ? data.acidPump : data.orpPump;
|
|
167
|
+
let pump = chem.pump;
|
|
168
|
+
switch (sys.board.valueMaps.chemPumpTypes.getName(pump.type)) {
|
|
169
|
+
case 'ezo-pmp':
|
|
170
|
+
if (typeof vals.dispense !== 'undefined') {
|
|
171
|
+
pump.ratedFlow = typeof vals.dispense.ratedFlow === 'number' ? vals.dispense.ratedFlow : pump.ratedFlow;
|
|
172
|
+
}
|
|
173
|
+
if (typeof vals.tank !== 'undefined') {
|
|
174
|
+
if (!isNaN(parseFloat(vals.tank.level))) schem.tank.level = parseFloat(vals.tank.level);
|
|
175
|
+
if (!isNaN(parseFloat(vals.tank.capacity))) schem.tank.capacity = chem.tank.capacity = parseFloat(vals.tank.capacity);
|
|
176
|
+
if (typeof vals.tank.units === 'string') schem.tank.units = chem.tank.units = vals.tank.units;
|
|
177
|
+
}
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
163
180
|
|
|
181
|
+
}
|
|
164
182
|
// Need to build this out to include the type of controller. If this is REM Chem we
|
|
165
183
|
// will send the whole rest of the nut over to it. Intellichem will only let us
|
|
166
184
|
// set specific values.
|
|
@@ -169,7 +187,7 @@ export class StateSocket {
|
|
|
169
187
|
}
|
|
170
188
|
}
|
|
171
189
|
}
|
|
172
|
-
catch (err) { logger.error(err); }
|
|
190
|
+
catch (err) { logger.error(`Socket processing error /chemController: ${err.message}`); }
|
|
173
191
|
});
|
|
174
192
|
sock.on('/circuit', async (data: any) => {
|
|
175
193
|
try {
|
|
@@ -189,7 +207,7 @@ export class StateSocket {
|
|
|
189
207
|
await sys.board.features.setFeatureStateAsync(id, utils.makeBool(data.isOn || typeof data.state));
|
|
190
208
|
}
|
|
191
209
|
}
|
|
192
|
-
catch (err) { logger.error(err); }
|
|
210
|
+
catch (err) { logger.error(`Socket processing error /feature: ${err.message}`); }
|
|
193
211
|
});
|
|
194
212
|
sock.on('/circuitGroup', async (data: any) => {
|
|
195
213
|
try {
|
|
@@ -199,7 +217,7 @@ export class StateSocket {
|
|
|
199
217
|
await sys.board.circuits.setCircuitGroupStateAsync(id, utils.makeBool(data.isOn || typeof data.state));
|
|
200
218
|
}
|
|
201
219
|
}
|
|
202
|
-
catch (err) { logger.error(err); }
|
|
220
|
+
catch (err) { logger.error(`Socket processing error /circuitGroup: ${err.message}`); }
|
|
203
221
|
});
|
|
204
222
|
sock.on('/lightGroup', async (data: any) => {
|
|
205
223
|
try {
|
|
@@ -210,9 +228,42 @@ export class StateSocket {
|
|
|
210
228
|
}
|
|
211
229
|
if (!isNaN(id) && typeof data.theme !== 'undefined') await sys.board.circuits.setLightGroupThemeAsync(id, data.theme);
|
|
212
230
|
}
|
|
213
|
-
catch (err) { logger.error(err); }
|
|
231
|
+
catch (err) { logger.error(`Socket processing error /lightGroup: ${err.message}`); }
|
|
232
|
+
});
|
|
233
|
+
sock.on('/panelMode', async (data: any) => {
|
|
234
|
+
try {
|
|
235
|
+
data = JSON.parse(data);
|
|
236
|
+
let obj = {};
|
|
237
|
+
if (typeof data.isOn !== 'undefined' && !utils.makeBool(data.isOn)) return; // This is just in case it is sent by REM for a toggle button.
|
|
238
|
+
if (typeof data.timeout !== 'undefined' && !isNaN(data.timeout) && data.timeout) {
|
|
239
|
+
switch (data.timeUnits.toLowerCase()) {
|
|
240
|
+
case 'min':
|
|
241
|
+
case 'mins':
|
|
242
|
+
case 'm':
|
|
243
|
+
case 'minute':
|
|
244
|
+
case 'minutes':
|
|
245
|
+
data.timeout = data.timeout * 60;
|
|
246
|
+
break;
|
|
247
|
+
case 'hr':
|
|
248
|
+
case 'hrs':
|
|
249
|
+
case 'h':
|
|
250
|
+
case 'hour':
|
|
251
|
+
data.timeout = data.timeout * 3600;
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (typeof data.mode === 'undefined' || data.mode === 'toggle') {
|
|
256
|
+
if (state.mode === 0) {
|
|
257
|
+
if (typeof data.timeout !== 'undefined' && !isNaN(data.timeout) && data.timeout)
|
|
258
|
+
data.mode = 'timeout';
|
|
259
|
+
else data.mode = 'service';
|
|
260
|
+
await sys.board.system.setPanelModeAsync(data);
|
|
261
|
+
}
|
|
262
|
+
else sys.board.system.setPanelModeAsync({ mode: 'auto' });
|
|
263
|
+
}
|
|
264
|
+
else await sys.board.system.setPanelModeAsync(data);
|
|
265
|
+
} catch (err) { logger.error(`Socket processing error /panelMode: ${err.message}`); }
|
|
214
266
|
});
|
|
215
|
-
|
|
216
267
|
/*
|
|
217
268
|
app.get('/state/chemController/:id', (req, res) => {
|
|
218
269
|
res.status(200).send(state.chemControllers.getItemById(parseInt(req.params.id, 10)).getExtended());
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* nodejs-poolController. An application to control pool equipment.
|
|
2
|
-
Copyright (C) 2016, 2017, 2018, 2019, 2020
|
|
2
|
+
Copyright (C) 2016, 2017, 2018, 2019, 2020, 2021, 2022.
|
|
3
|
+
Russell Goldin, tagyoureit. russ.goldin@gmail.com
|
|
3
4
|
|
|
4
5
|
This program is free software: you can redistribute it and/or modify
|
|
5
6
|
it under the terms of the GNU Affero General Public License as
|
|
@@ -14,11 +15,19 @@ GNU Affero General Public License for more details.
|
|
|
14
15
|
You should have received a copy of the GNU Affero General Public License
|
|
15
16
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
17
|
*/
|
|
18
|
+
import * as path from "path";
|
|
19
|
+
import * as fs from "fs";
|
|
20
|
+
import * as multer from 'multer';
|
|
17
21
|
import * as express from 'express';
|
|
18
22
|
import { SsdpServer} from '../../Server';
|
|
19
23
|
import { state } from "../../../controller/State";
|
|
20
24
|
import { sys } from "../../../controller/Equipment";
|
|
21
25
|
import { webApp } from "../../Server";
|
|
26
|
+
import { config } from "../../../config/Config";
|
|
27
|
+
import { logger } from "../../../logger/Logger";
|
|
28
|
+
import { ServiceParameterError, ServiceProcessError } from "../../../controller/Errors";
|
|
29
|
+
import { BindingsFile } from "../../interfaces/baseInterface";
|
|
30
|
+
import { Utils, utils } from "../../../controller/Constants";
|
|
22
31
|
const extend = require("extend");
|
|
23
32
|
export class UtilitiesRoute {
|
|
24
33
|
|
|
@@ -41,5 +50,184 @@ export class UtilitiesRoute {
|
|
|
41
50
|
}
|
|
42
51
|
return res.status(200).send(arr);
|
|
43
52
|
});
|
|
53
|
+
app.put('/app/interfaces/add', async (req, res, next) => {
|
|
54
|
+
try {
|
|
55
|
+
let faces = config.getSection('web.interfaces');
|
|
56
|
+
let opts: any = {};
|
|
57
|
+
switch (req.body.type) {
|
|
58
|
+
case 'rule':
|
|
59
|
+
opts = {};
|
|
60
|
+
break;
|
|
61
|
+
case 'rem':
|
|
62
|
+
opts = {
|
|
63
|
+
options: { protocol: 'http://', host: '', port: 8080, headers: { "content-type": "application/json" } },
|
|
64
|
+
socket: {
|
|
65
|
+
transports: ['websocket'], allowEIO3: true, upgrade: false,
|
|
66
|
+
reconnectionDelay: 2000, reconnection: true, reconnectionDelayMax: 20000
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
break;
|
|
70
|
+
case 'http':
|
|
71
|
+
case 'rest':
|
|
72
|
+
opts = {
|
|
73
|
+
options: { protocol: 'http://', host: '', port: 80 }
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
case 'influx':
|
|
77
|
+
case 'influxdb':
|
|
78
|
+
opts = {
|
|
79
|
+
options: {
|
|
80
|
+
version: 1,
|
|
81
|
+
protocol: 'http',
|
|
82
|
+
database: 'pool',
|
|
83
|
+
port: 8601,
|
|
84
|
+
retentionPolicy: 'autogen'
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
case 'influxdb2':
|
|
89
|
+
opts = {
|
|
90
|
+
options: {
|
|
91
|
+
version: 2,
|
|
92
|
+
protocol: 'http',
|
|
93
|
+
port: 9999,
|
|
94
|
+
database: 'pool',
|
|
95
|
+
bucket: '57ec4eed2d90a50b',
|
|
96
|
+
token: '...LuyM84JJx93Qvc7tfaXPbI_mFFjRBjaA==',
|
|
97
|
+
org: 'njsPC-org'
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
case 'mqtt':
|
|
102
|
+
opts = {
|
|
103
|
+
options: {
|
|
104
|
+
protocol: 'mqtt://', host: '', port: 1883, username: '', password: '',
|
|
105
|
+
selfSignedCertificate: false,
|
|
106
|
+
rootTopic: "pool/@bind=(state.equipment.model).replace(' ','-').replace(' / ','').toLowerCase();",
|
|
107
|
+
retain: true, qos: 0, changesOnly: true
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
return Promise.reject(new ServiceParameterError(`An invalid type was specified ${req.body.type}`, 'PUT: /app/interfaces/add', 'type', req.body.type));
|
|
113
|
+
}
|
|
114
|
+
opts.uuid = utils.uuid();
|
|
115
|
+
opts.isCustom = true;
|
|
116
|
+
// Now lets create a name for the element. This would have been much easier if it were an array but alas we are stuck.
|
|
117
|
+
let name = req.body.name.replace(/^[^a-zA-Z_$]|[^0-9a-zA-Z_$]/g, '_');
|
|
118
|
+
if (name.length === 0) return Promise.reject(new ServiceParameterError(`An invalid name was specified ${req.body.name}`, 'PUT: /app/interfaces/add', 'name', req.body.name));
|
|
119
|
+
if (name.charAt(0) >= '0' && name.charAt(0) <= '9') name = 'i' + name;
|
|
120
|
+
let fnEnsureUnique = (name, ord?: number): string => {
|
|
121
|
+
let isUnique = true;
|
|
122
|
+
for (let fname in faces) {
|
|
123
|
+
if (fname === (typeof ord !== 'undefined' ? `${name}_${ord}` : name)) {
|
|
124
|
+
isUnique = false;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!isUnique) name = fnEnsureUnique(name, typeof ord === 'undefined' ? 0 : ord++);
|
|
129
|
+
return typeof ord !== 'undefined' ? `${name}_${ord}` : name;
|
|
130
|
+
}
|
|
131
|
+
name = fnEnsureUnique(name);
|
|
132
|
+
opts = extend(true, {}, opts, req.body);
|
|
133
|
+
config.setSection(`web.interfaces.${name}`, opts);
|
|
134
|
+
res.status(200).send({ id: name, opts: opts });
|
|
135
|
+
} catch (err) { next(err); }
|
|
136
|
+
});
|
|
137
|
+
app.delete('/app/interface', async (req, res, next) => {
|
|
138
|
+
try {
|
|
139
|
+
let faces = config.getSection('web.interfaces');
|
|
140
|
+
let deleted;
|
|
141
|
+
for (let fname in faces) {
|
|
142
|
+
let iface = faces[fname];
|
|
143
|
+
if (typeof req.body.id !== 'undefined') {
|
|
144
|
+
if (fname === req.body.id) {
|
|
145
|
+
deleted = iface;
|
|
146
|
+
iface.enabled = false;
|
|
147
|
+
await webApp.updateServerInterface(iface);
|
|
148
|
+
config.removeSection(`web.interfaces.${fname}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else if (typeof req.body.uuid !== 'undefined') {
|
|
152
|
+
if (req.body.uuid.toLowerCase() === iface.uuid.toLowerCase()) {
|
|
153
|
+
deleted = iface;
|
|
154
|
+
iface.enabled = false;
|
|
155
|
+
await webApp.updateServerInterface(iface);
|
|
156
|
+
config.removeSection(`web.interfaces.${fname}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return res.status(200).send(deleted);
|
|
161
|
+
} catch (err) { next(err); }
|
|
162
|
+
});
|
|
163
|
+
app.get('/app/options/interfaces', async (req, res, next) => {
|
|
164
|
+
try {
|
|
165
|
+
// todo: move bytevaluemaps out to a proper location; add additional definitions
|
|
166
|
+
let opts = {
|
|
167
|
+
interfaces: config.getSection('web.interfaces'),
|
|
168
|
+
types: [
|
|
169
|
+
{ name: 'rule', desc: 'Rule', hasBindings: true, hasUrl: false },
|
|
170
|
+
{ name: 'rest', desc: 'Rest', hasBindings: true },
|
|
171
|
+
{ name: 'http', desc: 'Http', hasBindings: true },
|
|
172
|
+
{ name: 'rem', desc: 'Relay Equipment Manager', hasBindings: false },
|
|
173
|
+
{ name: 'mqtt', desc: 'MQTT', hasBindings: true },
|
|
174
|
+
{ name: 'influx', desc: 'InfluxDB', hasBindings: true },
|
|
175
|
+
{ name: 'influxdb2', desc: 'InfluxDB2', hasBindings: true}
|
|
176
|
+
],
|
|
177
|
+
protocols: [
|
|
178
|
+
{ val: 0, name: 'http://', desc: 'http://' },
|
|
179
|
+
{ val: 1, name: 'https://', desc: 'https://' },
|
|
180
|
+
{ val: 2, name: 'mqtt://', desc: 'mqtt://' }
|
|
181
|
+
],
|
|
182
|
+
files: []
|
|
183
|
+
}
|
|
184
|
+
// Read all the files in the custom bindings directory.
|
|
185
|
+
let cpath = path.posix.join(process.cwd(), '/web/bindings/custom/');
|
|
186
|
+
let files = fs.readdirSync(cpath);
|
|
187
|
+
for (let i = 0; i < files.length; i++) {
|
|
188
|
+
if (path.extname(files[i]) === '.json') {
|
|
189
|
+
let bf = await BindingsFile.fromFile(path.posix.join(process.cwd(), 'web/bindings/'), `custom/${files[i]}`);
|
|
190
|
+
if (typeof bf !== 'undefined') opts.files.push(bf.options);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return res.status(200).send(opts);
|
|
194
|
+
} catch (err) { next(err); }
|
|
195
|
+
});
|
|
196
|
+
app.post('/app/interfaceBindings/file', async (req, res, next) => {
|
|
197
|
+
try {
|
|
198
|
+
let file = multer({
|
|
199
|
+
limits: { fileSize: 1000000 },
|
|
200
|
+
storage: multer.memoryStorage()
|
|
201
|
+
}).single('bindingsFile');
|
|
202
|
+
file(req, res, async (err) => {
|
|
203
|
+
try {
|
|
204
|
+
if (err) { next(err); }
|
|
205
|
+
else {
|
|
206
|
+
// Validate the incoming data and save it off only if it is valid.
|
|
207
|
+
let bf = await BindingsFile.fromBuffer(req.file.originalname, req.file.buffer);
|
|
208
|
+
if (typeof bf === 'undefined') {
|
|
209
|
+
err = new ServiceProcessError(`Invalid bindings file: ${req.file.originalname}`, 'POST: app/bindings/file', 'extractBindingOptions');
|
|
210
|
+
next(err);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
if (fs.existsSync(bf.filePath))
|
|
214
|
+
return next(new ServiceProcessError(`File already exists ${req.file.originalname}`, 'POST: app/bindings/file', 'writeFile'));
|
|
215
|
+
else {
|
|
216
|
+
try {
|
|
217
|
+
fs.writeFileSync(bf.filePath, req.file.buffer);
|
|
218
|
+
} catch (e) { logger.error(`Error writing bindings file ${e.message}`); }
|
|
219
|
+
}
|
|
220
|
+
return res.status(200).send(bf);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
} catch (e) {
|
|
224
|
+
err = new ServiceProcessError(`Error uploading file: ${e.message}`, 'POST: app/backup/file', 'uploadFile');
|
|
225
|
+
next(err);
|
|
226
|
+
logger.error(e);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
} catch (err) { next(err); }
|
|
230
|
+
});
|
|
231
|
+
|
|
44
232
|
}
|
|
45
233
|
}
|