nodejs-poolcontroller 8.3.0 → 8.4.1

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 (107) 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 -63
  7. package/.github/workflows/ghcr-publish.yml +67 -67
  8. package/157_issues.md +101 -0
  9. package/AGENTS.md +613 -0
  10. package/CONTRIBUTING.md +74 -74
  11. package/Changelog +292 -284
  12. package/Dockerfile +62 -62
  13. package/Gruntfile.js +40 -40
  14. package/LICENSE +661 -661
  15. package/README.md +329 -309
  16. package/anslq25/MessagesMock.ts +221 -221
  17. package/anslq25/boards/MockBoardFactory.ts +49 -49
  18. package/anslq25/boards/MockEasyTouchBoard.ts +696 -696
  19. package/anslq25/boards/MockSystemBoard.ts +216 -216
  20. package/anslq25/chemistry/MockChlorinator.ts +98 -98
  21. package/anslq25/pumps/MockPump.ts +83 -83
  22. package/app.ts +115 -115
  23. package/config/Config.ts +0 -0
  24. package/config/VersionCheck.ts +0 -0
  25. package/controller/Constants.ts +809 -805
  26. package/controller/Equipment.ts +2737 -2664
  27. package/controller/Errors.ts +181 -181
  28. package/controller/Lockouts.ts +549 -549
  29. package/controller/State.ts +3746 -3701
  30. package/controller/boards/AquaLinkBoard.ts +1175 -1003
  31. package/controller/boards/BoardFactory.ts +53 -53
  32. package/controller/boards/EasyTouchBoard.ts +3246 -3202
  33. package/controller/boards/IntelliCenterBoard.ts +4581 -3899
  34. package/controller/boards/IntelliComBoard.ts +69 -69
  35. package/controller/boards/IntelliTouchBoard.ts +382 -382
  36. package/controller/boards/NixieBoard.ts +1947 -1944
  37. package/controller/boards/SunTouchBoard.ts +401 -400
  38. package/controller/boards/SystemBoard.ts +5303 -5268
  39. package/controller/comms/Comms.ts +1278 -1255
  40. package/controller/comms/ScreenLogic.ts +1665 -1665
  41. package/controller/comms/messages/Messages.ts +1627 -1406
  42. package/controller/comms/messages/config/ChlorinatorMessage.ts +5 -0
  43. package/controller/comms/messages/config/CircuitGroupMessage.ts +0 -0
  44. package/controller/comms/messages/config/CircuitMessage.ts +0 -0
  45. package/controller/comms/messages/config/ConfigMessage.ts +6 -0
  46. package/controller/comms/messages/config/CoverMessage.ts +0 -0
  47. package/controller/comms/messages/config/CustomNameMessage.ts +31 -31
  48. package/controller/comms/messages/config/EquipmentMessage.ts +250 -210
  49. package/controller/comms/messages/config/ExternalMessage.ts +1051 -903
  50. package/controller/comms/messages/config/FeatureMessage.ts +0 -0
  51. package/controller/comms/messages/config/GeneralMessage.ts +65 -0
  52. package/controller/comms/messages/config/HeaterMessage.ts +0 -0
  53. package/controller/comms/messages/config/IntellichemMessage.ts +0 -0
  54. package/controller/comms/messages/config/OptionsMessage.ts +207 -174
  55. package/controller/comms/messages/config/PumpMessage.ts +427 -421
  56. package/controller/comms/messages/config/RemoteMessage.ts +0 -0
  57. package/controller/comms/messages/config/ScheduleMessage.ts +401 -390
  58. package/controller/comms/messages/config/SecurityMessage.ts +37 -13
  59. package/controller/comms/messages/config/ValveMessage.ts +0 -0
  60. package/controller/comms/messages/status/ChlorinatorStateMessage.ts +0 -0
  61. package/controller/comms/messages/status/EquipmentStateMessage.ts +940 -822
  62. package/controller/comms/messages/status/HeaterStateMessage.ts +147 -135
  63. package/controller/comms/messages/status/IntelliChemStateMessage.ts +448 -448
  64. package/controller/comms/messages/status/IntelliValveStateMessage.ts +36 -36
  65. package/controller/comms/messages/status/NeptuneModbusStateMessage.ts +217 -0
  66. package/controller/comms/messages/status/PumpStateMessage.ts +0 -0
  67. package/controller/comms/messages/status/RegalModbusStateMessage.ts +410 -410
  68. package/controller/comms/messages/status/VersionMessage.ts +152 -41
  69. package/controller/nixie/Nixie.ts +173 -173
  70. package/controller/nixie/NixieEquipment.ts +104 -104
  71. package/controller/nixie/bodies/Body.ts +120 -120
  72. package/controller/nixie/bodies/Filter.ts +135 -135
  73. package/controller/nixie/chemistry/ChemController.ts +2756 -2724
  74. package/controller/nixie/chemistry/ChemDoser.ts +806 -806
  75. package/controller/nixie/chemistry/Chlorinator.ts +367 -367
  76. package/controller/nixie/circuits/Circuit.ts +478 -478
  77. package/controller/nixie/heaters/Heater.ts +843 -834
  78. package/controller/nixie/pumps/Pump.ts +1336 -1193
  79. package/controller/nixie/schedules/Schedule.ts +401 -401
  80. package/controller/nixie/valves/Valve.ts +170 -170
  81. package/defaultConfig.json +352 -352
  82. package/docker-compose.yml +32 -31
  83. package/logger/DataLogger.ts +448 -448
  84. package/logger/Logger.ts +459 -436
  85. package/package.json +58 -58
  86. package/sendSocket.js +32 -32
  87. package/tsconfig.json +26 -25
  88. package/types/express-multer.d.ts +32 -32
  89. package/web/Server.ts +1939 -1927
  90. package/web/bindings/aqualinkD.json +559 -559
  91. package/web/bindings/influxDB.json +1066 -1066
  92. package/web/bindings/mqtt.json +721 -721
  93. package/web/bindings/mqttAlt.json +746 -746
  94. package/web/bindings/rulesManager.json +54 -54
  95. package/web/bindings/smartThings-Hubitat.json +31 -31
  96. package/web/bindings/valveRelays.json +20 -20
  97. package/web/bindings/vera.json +25 -25
  98. package/web/interfaces/baseInterface.ts +188 -188
  99. package/web/interfaces/httpInterface.ts +148 -148
  100. package/web/interfaces/influxInterface.ts +283 -283
  101. package/web/interfaces/mqttInterface.ts +695 -695
  102. package/web/interfaces/ruleInterface.ts +101 -87
  103. package/web/services/config/Config.ts +1212 -1053
  104. package/web/services/config/ConfigSocket.ts +0 -0
  105. package/web/services/state/State.ts +21 -0
  106. package/web/services/state/StateSocket.ts +28 -0
  107. package/web/services/utilities/Utilities.ts +233 -233
File without changes
@@ -399,6 +399,27 @@ export class StateRoute {
399
399
  }
400
400
  catch (err) { next(err); }
401
401
  });
402
+ app.put('/state/light/setBrightness', async (req, res, next) => {
403
+ try {
404
+ let cstate = await sys.board.circuits.setDimmerLevelAsync(
405
+ parseInt(req.body.id, 10),
406
+ parseInt(typeof req.body.level !== 'undefined' ? req.body.level : req.body.brightness, 10)
407
+ );
408
+ return res.status(200).send(cstate.get(true));
409
+ }
410
+ catch (err) { next(err); }
411
+ });
412
+ app.put('/state/light/setColor', async (req, res, next) => {
413
+ try {
414
+ let cstate = await sys.board.circuits.setLightColorAsync(parseInt(req.body.id, 10), {
415
+ red: parseInt(typeof req.body.red !== 'undefined' ? req.body.red : req.body.r, 10),
416
+ green: parseInt(typeof req.body.green !== 'undefined' ? req.body.green : req.body.g, 10),
417
+ blue: parseInt(typeof req.body.blue !== 'undefined' ? req.body.blue : req.body.b, 10)
418
+ });
419
+ return res.status(200).send(cstate.get(true));
420
+ }
421
+ catch (err) { next(err); }
422
+ });
402
423
  app.put('/state/feature/setState', async (req, res, next) => {
403
424
  try {
404
425
  let isOn = utils.makeBool(typeof req.body.isOn !== 'undefined' ? req.body.isOn : req.body.state);
@@ -300,6 +300,13 @@ export class StateSocket {
300
300
  }
301
301
  catch (err) { next(err); }
302
302
  });
303
+ app.put('/state/light/setTheme', async (req, res, next) => {
304
+ try {
305
+ let theme = await state.circuits.setLightThemeAsync(parseInt(req.body.id, 10), parseInt(req.body.theme, 10));
306
+ return res.status(200).send(theme);
307
+ }
308
+ catch (err) { next(err); }
309
+ });
303
310
 
304
311
  app.put('/state/circuit/setDimmerLevel', async (req, res, next) => {
305
312
  try {
@@ -308,6 +315,27 @@ export class StateSocket {
308
315
  }
309
316
  catch (err) { next(err); }
310
317
  });
318
+ app.put('/state/light/setBrightness', async (req, res, next) => {
319
+ try {
320
+ let circuit = await sys.board.circuits.setDimmerLevelAsync(
321
+ parseInt(req.body.id, 10),
322
+ parseInt(typeof req.body.level !== 'undefined' ? req.body.level : req.body.brightness, 10)
323
+ );
324
+ return res.status(200).send(circuit);
325
+ }
326
+ catch (err) { next(err); }
327
+ });
328
+ app.put('/state/light/setColor', async (req, res, next) => {
329
+ try {
330
+ let circuit = await sys.board.circuits.setLightColorAsync(parseInt(req.body.id, 10), {
331
+ red: parseInt(typeof req.body.red !== 'undefined' ? req.body.red : req.body.r, 10),
332
+ green: parseInt(typeof req.body.green !== 'undefined' ? req.body.green : req.body.g, 10),
333
+ blue: parseInt(typeof req.body.blue !== 'undefined' ? req.body.blue : req.body.b, 10)
334
+ });
335
+ return res.status(200).send(circuit);
336
+ }
337
+ catch (err) { next(err); }
338
+ });
311
339
  app.put('/state/feature/setState', async (req, res, next) => {
312
340
  try {
313
341
  await state.features.setFeatureStateAsync(req.body.id, req.body.state);
@@ -1,233 +1,233 @@
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 path from "path";
19
- import * as fs from "fs";
20
- import * as multer from 'multer';
21
- import * as express from 'express';
22
- import { SsdpServer} from '../../Server';
23
- import { state } from "../../../controller/State";
24
- import { sys } from "../../../controller/Equipment";
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";
31
- const extend = require("extend");
32
- export class UtilitiesRoute {
33
-
34
- public static initRoutes(app: express.Application) {
35
- app.use('/upnp.xml', async (req, res, next) => {
36
- try {
37
- // Put together the upnp device description.
38
- let ssdp = webApp.findServer('ssdp') as SsdpServer;
39
- if (typeof ssdp === 'undefined') throw new Error(`SSDP Server not initialized. No upnp information available.`);
40
- res.status(200).set('Content-Type', 'text/xml').send(ssdp.deviceXML());
41
- } catch (err) { next(err); }
42
- });
43
- app.get('/extended/:section', (req, res) => {
44
- let cfg = sys.getSection(req.params.section);
45
- let st = state.getState(req.params.section);
46
- let arr = [];
47
- for (let i = 0; i < cfg.length; i++){
48
- let p = extend(true, {}, cfg[i], st.find(s => s.id === cfg[i].id));
49
- arr.push(p);
50
- }
51
- return res.status(200).send(arr);
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(/ /g,'-').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(`File upload error: ${e.message}`);
227
- }
228
- });
229
- } catch (err) { next(err); }
230
- });
231
-
232
- }
233
- }
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 path from "path";
19
+ import * as fs from "fs";
20
+ import * as multer from 'multer';
21
+ import * as express from 'express';
22
+ import { SsdpServer} from '../../Server';
23
+ import { state } from "../../../controller/State";
24
+ import { sys } from "../../../controller/Equipment";
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";
31
+ const extend = require("extend");
32
+ export class UtilitiesRoute {
33
+
34
+ public static initRoutes(app: express.Application) {
35
+ app.use('/upnp.xml', async (req, res, next) => {
36
+ try {
37
+ // Put together the upnp device description.
38
+ let ssdp = webApp.findServer('ssdp') as SsdpServer;
39
+ if (typeof ssdp === 'undefined') throw new Error(`SSDP Server not initialized. No upnp information available.`);
40
+ res.status(200).set('Content-Type', 'text/xml').send(ssdp.deviceXML());
41
+ } catch (err) { next(err); }
42
+ });
43
+ app.get('/extended/:section', (req, res) => {
44
+ let cfg = sys.getSection(req.params.section);
45
+ let st = state.getState(req.params.section);
46
+ let arr = [];
47
+ for (let i = 0; i < cfg.length; i++){
48
+ let p = extend(true, {}, cfg[i], st.find(s => s.id === cfg[i].id));
49
+ arr.push(p);
50
+ }
51
+ return res.status(200).send(arr);
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(/ /g,'-').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(`File upload error: ${e.message}`);
227
+ }
228
+ });
229
+ } catch (err) { next(err); }
230
+ });
231
+
232
+ }
233
+ }