homebridge-smartsystem 6.7.4 → 6.8.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/README.md +3 -0
- package/config.json +10 -1
- package/duotecno/types.js +15 -0
- package/duotecno/types.ts +15 -0
- package/package.json +1 -1
- package/server/platform.js +11 -0
- package/server/platform.ts +11 -1
- package/server/proxy.js +257 -0
- package/server/proxy.ts +315 -0
- package/server/smartapp.js +30 -0
- package/server/smartapp.ts +32 -0
- package/server/views/nav.ejs +1 -0
- package/server/views/proxy.ejs +71 -0
package/README.md
CHANGED
|
@@ -301,6 +301,9 @@ Homebridge UI version
|
|
|
301
301
|
- 3: do dimmers/up-downs
|
|
302
302
|
- 4: better up-down values to and from HB
|
|
303
303
|
|
|
304
|
+
### v6.8.0 - 08/07/2025
|
|
305
|
+
- 0: first version including the proxy
|
|
306
|
+
|
|
304
307
|
## Hardware
|
|
305
308
|
A Raspberry Pi that connects to a Duotecno IP Node
|
|
306
309
|
and (if configured) to a Smappee Infinity (power and plugs)
|
package/config.json
CHANGED
|
@@ -480,5 +480,14 @@
|
|
|
480
480
|
"gateway2": ""
|
|
481
481
|
}
|
|
482
482
|
},
|
|
483
|
-
"p1": false
|
|
483
|
+
"p1": false,
|
|
484
|
+
"proxy": {
|
|
485
|
+
"cloudServer": "ws.duotecno.eu",
|
|
486
|
+
"cloudPort": 5098,
|
|
487
|
+
"masterAddress": "192.168.0.97",
|
|
488
|
+
"masterPort": 5001,
|
|
489
|
+
"uniqueId": "",
|
|
490
|
+
"debug": false,
|
|
491
|
+
"kind": "solo"
|
|
492
|
+
}
|
|
484
493
|
}
|
package/duotecno/types.js
CHANGED
|
@@ -15,6 +15,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.makeInt = exports.now = exports.datetime = exports.time = exports.date = exports.watt = exports.hex = exports.two = exports.char = exports.ascii = exports.wait = exports.Sanitizers = exports.actionValueStr = exports.actionValue = exports.kEmptyBinding = exports.kEmptyLink = exports.LinkType = exports.kEmptySwitch = exports.SwitchType = exports.kEmptyRule = exports.kEmptyAction = exports.RuleType = exports.Boundaries = exports.kEmptyCommRecord = exports.WriteError = exports.kEmptySettings = exports.kEmptyNetworkConfig = exports.kEmptyGroup = exports.kEmptyScene = exports.kEmptyUnitScene = exports.kEmptyUnit = exports.UnitExtendedType = exports.UnitType = exports.UnitMotorCmd = exports.UnitState = exports.NodeType = void 0;
|
|
18
|
+
const proxy_1 = require("../server/proxy");
|
|
18
19
|
// Node types
|
|
19
20
|
var NodeType;
|
|
20
21
|
(function (NodeType) {
|
|
@@ -204,6 +205,7 @@ exports.Sanitizers = {
|
|
|
204
205
|
config.shelly = config.shelly || false;
|
|
205
206
|
config.smartapp = config.smartapp || null;
|
|
206
207
|
config.system = exports.Sanitizers.system(config.system);
|
|
208
|
+
config.proxy = exports.Sanitizers.proxy(config.proxy, config.system);
|
|
207
209
|
return config;
|
|
208
210
|
},
|
|
209
211
|
homebridge: function (config) {
|
|
@@ -358,6 +360,19 @@ exports.Sanitizers = {
|
|
|
358
360
|
b.channel = binding.channel || "0";
|
|
359
361
|
return b;
|
|
360
362
|
},
|
|
363
|
+
///////////
|
|
364
|
+
// Proxy //
|
|
365
|
+
///////////
|
|
366
|
+
proxy: function (config, system) {
|
|
367
|
+
var _a, _b;
|
|
368
|
+
const c = proxy_1.kEmptyProxy;
|
|
369
|
+
c.cloudServer = (config === null || config === void 0 ? void 0 : config.cloudServer) || proxy_1.kEmptyProxy.cloudServer;
|
|
370
|
+
c.cloudPort = (config === null || config === void 0 ? void 0 : config.cloudPort) || proxy_1.kEmptyProxy.cloudPort;
|
|
371
|
+
c.masterAddress = (config === null || config === void 0 ? void 0 : config.masterAddress) || ((_a = system === null || system === void 0 ? void 0 : system.cmasters[0]) === null || _a === void 0 ? void 0 : _a.address) || "";
|
|
372
|
+
c.masterPort = (config === null || config === void 0 ? void 0 : config.masterPort) || ((_b = system === null || system === void 0 ? void 0 : system.cmasters[0]) === null || _b === void 0 ? void 0 : _b.port) || 5001;
|
|
373
|
+
c.uniqueId = (config === null || config === void 0 ? void 0 : config.uniqueId) || proxy_1.kEmptyProxy.uniqueId;
|
|
374
|
+
return c;
|
|
375
|
+
},
|
|
361
376
|
//////////////
|
|
362
377
|
// Duotecno //
|
|
363
378
|
//////////////
|
package/duotecno/types.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// v3 - smart server version, Mar 2020
|
|
5
5
|
// v3.1 - added scenes from app version, May 2020
|
|
6
6
|
|
|
7
|
+
import { kEmptyProxy, ProxyConfig } from "../server/proxy";
|
|
7
8
|
import { Unit } from "./protocol";
|
|
8
9
|
|
|
9
10
|
// for Active=Y/N or New=X
|
|
@@ -412,6 +413,7 @@ export const Sanitizers = {
|
|
|
412
413
|
config.shelly = config.shelly || false;
|
|
413
414
|
config.smartapp = config.smartapp || null;
|
|
414
415
|
config.system = Sanitizers.system(config.system);
|
|
416
|
+
config.proxy = Sanitizers.proxy(config.proxy, config.system);
|
|
415
417
|
|
|
416
418
|
return config;
|
|
417
419
|
},
|
|
@@ -581,6 +583,19 @@ export const Sanitizers = {
|
|
|
581
583
|
return b;
|
|
582
584
|
},
|
|
583
585
|
|
|
586
|
+
///////////
|
|
587
|
+
// Proxy //
|
|
588
|
+
///////////
|
|
589
|
+
proxy: function(config?: ProxyConfig, system?: SystemConfig): ProxyConfig {
|
|
590
|
+
const c = kEmptyProxy as ProxyConfig;
|
|
591
|
+
c.cloudServer = config?.cloudServer || kEmptyProxy.cloudServer;
|
|
592
|
+
c.cloudPort = config?.cloudPort || kEmptyProxy.cloudPort;
|
|
593
|
+
c.masterAddress = config?.masterAddress || system?.cmasters[0]?.address || "";
|
|
594
|
+
c.masterPort = config?.masterPort || system?.cmasters[0]?.port || 5001;
|
|
595
|
+
c.uniqueId = config?.uniqueId || kEmptyProxy.uniqueId;
|
|
596
|
+
return c;
|
|
597
|
+
},
|
|
598
|
+
|
|
584
599
|
//////////////
|
|
585
600
|
// Duotecno //
|
|
586
601
|
//////////////
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-smartsystem",
|
|
3
3
|
"displayname": "Duotecno Bridge",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.8.0",
|
|
5
5
|
"description": "SmartServer (Proxy Websockets to TCP sockets, Smappee MQTT, Duotecno IP Nodes, Homekit interface)",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Johan Coppieters",
|
package/server/platform.js
CHANGED
|
@@ -30,8 +30,10 @@ const p1_1 = require("./p1");
|
|
|
30
30
|
const shelly_1 = require("./shelly");
|
|
31
31
|
const config_1 = require("../duotecno/config");
|
|
32
32
|
const fs_1 = require("fs");
|
|
33
|
+
const proxy_1 = require("./proxy");
|
|
33
34
|
class Platform extends base_1.Base {
|
|
34
35
|
constructor(logger, pConfig, api) {
|
|
36
|
+
var _a;
|
|
35
37
|
super("homebridge");
|
|
36
38
|
this.accessoryList = [];
|
|
37
39
|
this.ready = false;
|
|
@@ -109,6 +111,15 @@ class Platform extends base_1.Base {
|
|
|
109
111
|
else {
|
|
110
112
|
(0, logger_1.log)("homebridge", "platform - No SocApp configured.");
|
|
111
113
|
}
|
|
114
|
+
if (this.config.proxy && ((_a = this.config.proxy) === null || _a === void 0 ? void 0 : _a.uniqueId)) {
|
|
115
|
+
this.config.proxy.kind = "gw";
|
|
116
|
+
(0, proxy_1.setProxyConfig)(this.config.proxy);
|
|
117
|
+
(0, proxy_1.cleanStart)(true);
|
|
118
|
+
(0, logger_1.log)("homebridge", "platform - Proxy configured: " + JSON.stringify(this.config.proxy, null, 2));
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
(0, logger_1.log)("homebridge", "platform - No Proxy configured.");
|
|
122
|
+
}
|
|
112
123
|
}
|
|
113
124
|
startPower(type, PowerMgr) {
|
|
114
125
|
// startup a power handler, if configured
|
package/server/platform.ts
CHANGED
|
@@ -20,7 +20,8 @@ import { Lock } from "../accessories/lock";
|
|
|
20
20
|
import { P1 } from "./p1";
|
|
21
21
|
import { Shelly } from "./shelly";
|
|
22
22
|
import { kConfigFiles, setConfig } from "../duotecno/config";
|
|
23
|
-
import {
|
|
23
|
+
import { readFileSync } from "fs";
|
|
24
|
+
import { cleanStart, setProxyConfig } from "./proxy";
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
export class Platform extends Base {
|
|
@@ -115,6 +116,15 @@ export class Platform extends Base {
|
|
|
115
116
|
} else {
|
|
116
117
|
log("homebridge", "platform - No SocApp configured.");
|
|
117
118
|
}
|
|
119
|
+
|
|
120
|
+
if (this.config.proxy && this.config.proxy?.uniqueId) {
|
|
121
|
+
this.config.proxy.kind = "gw";
|
|
122
|
+
setProxyConfig(this.config.proxy);
|
|
123
|
+
cleanStart(true);
|
|
124
|
+
log("homebridge", "platform - Proxy configured: " + JSON.stringify(this.config.proxy, null, 2));
|
|
125
|
+
} else {
|
|
126
|
+
log("homebridge", "platform - No Proxy configured.");
|
|
127
|
+
}
|
|
118
128
|
}
|
|
119
129
|
|
|
120
130
|
startPower(type: string, PowerMgr: any) {
|
package/server/proxy.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Context = exports.cleanStart = exports.makeNewCloudConnection = exports.setProxyConfig = exports.kEmptyProxy = void 0;
|
|
4
|
+
const net = require("net");
|
|
5
|
+
const WebSocket = require("ws");
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
const process_1 = require("process");
|
|
8
|
+
exports.kEmptyProxy = {
|
|
9
|
+
cloudServer: "ws.duotecno.eu",
|
|
10
|
+
cloudPort: 5098,
|
|
11
|
+
masterAddress: "",
|
|
12
|
+
masterPort: 5001,
|
|
13
|
+
uniqueId: "",
|
|
14
|
+
debug: false,
|
|
15
|
+
kind: "solo" // default running under PM2 or other process manager -> don't restart yourself
|
|
16
|
+
};
|
|
17
|
+
function setProxyConfig(c) {
|
|
18
|
+
// set the proxy config
|
|
19
|
+
if (c) {
|
|
20
|
+
config.cloudServer = c.cloudServer || exports.kEmptyProxy.cloudServer;
|
|
21
|
+
config.cloudPort = c.cloudPort || exports.kEmptyProxy.cloudPort;
|
|
22
|
+
config.masterAddress = c.masterAddress || exports.kEmptyProxy.masterAddress;
|
|
23
|
+
config.masterPort = c.masterPort || exports.kEmptyProxy.masterPort;
|
|
24
|
+
config.uniqueId = c.uniqueId || exports.kEmptyProxy.uniqueId;
|
|
25
|
+
config.debug = !!c.debug;
|
|
26
|
+
config.kind = c.kind || exports.kEmptyProxy.kind;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
config = Object.assign({}, exports.kEmptyProxy);
|
|
30
|
+
}
|
|
31
|
+
return config;
|
|
32
|
+
}
|
|
33
|
+
exports.setProxyConfig = setProxyConfig;
|
|
34
|
+
let config;
|
|
35
|
+
try {
|
|
36
|
+
setProxyConfig(require('../config-proxy.json'));
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
setProxyConfig();
|
|
40
|
+
console.log("No config-proxy.json found, using default values from homebridge or other.");
|
|
41
|
+
}
|
|
42
|
+
const gCloudConnections = [];
|
|
43
|
+
const kDebug = config.debug || false; // enable debug mode from config
|
|
44
|
+
/////////////
|
|
45
|
+
// Logging //
|
|
46
|
+
/////////////
|
|
47
|
+
function debug(str) {
|
|
48
|
+
if (kDebug) {
|
|
49
|
+
console.log(`DEBUG: ${str}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function warning(str) {
|
|
53
|
+
console.warn(`WARNING: ${str}`);
|
|
54
|
+
}
|
|
55
|
+
function log(str) {
|
|
56
|
+
console.log(`LOG: ${str}`);
|
|
57
|
+
}
|
|
58
|
+
function error(str) {
|
|
59
|
+
console.error(`ERROR: **** ${str} ****`);
|
|
60
|
+
}
|
|
61
|
+
///////////////////////////////
|
|
62
|
+
// Start up the proxy server //
|
|
63
|
+
///////////////////////////////
|
|
64
|
+
// if (config.kind != "gw") {
|
|
65
|
+
// makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
66
|
+
// }
|
|
67
|
+
function makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId) {
|
|
68
|
+
const cloudSocket = new WebSocket('ws://' + server + ':' + serverPort + "/" + uniqueId);
|
|
69
|
+
cloudSocket.on('open', () => {
|
|
70
|
+
log(`[PROXY] → [CLOUD] Connected to Cloud at ${server}:${serverPort}`);
|
|
71
|
+
const context = new Context(cloudSocket, master, masterPort, server, serverPort, uniqueId);
|
|
72
|
+
gCloudConnections.push(context);
|
|
73
|
+
log(`[PROXY] → [CLOUD] New free connection #${gCloudConnections.length} to the Cloud at ${server}:${serverPort}`);
|
|
74
|
+
});
|
|
75
|
+
log(`[PROXY] → [CLOUD] Attempting to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
|
|
76
|
+
}
|
|
77
|
+
exports.makeNewCloudConnection = makeNewCloudConnection;
|
|
78
|
+
////////////////
|
|
79
|
+
// Restarting //
|
|
80
|
+
////////////////
|
|
81
|
+
function cleanStart(restart = false) {
|
|
82
|
+
if (gCloudConnections.length) {
|
|
83
|
+
gCloudConnections.forEach(context => {
|
|
84
|
+
if (context.deviceSocket) {
|
|
85
|
+
context.deviceSocket.end();
|
|
86
|
+
context.deviceSocket = null;
|
|
87
|
+
}
|
|
88
|
+
context.cloudSocket.close();
|
|
89
|
+
context.cloudSocket = null;
|
|
90
|
+
});
|
|
91
|
+
gCloudConnections.splice(0, gCloudConnections.length); // clear the array
|
|
92
|
+
log(`[PROXY] → [CLOUD] Cleaned up all cloud connections.`);
|
|
93
|
+
}
|
|
94
|
+
if (restart) {
|
|
95
|
+
if (config.uniqueId) {
|
|
96
|
+
makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
log(`[PROXY] → [CLOUD] Not restarting, no unique ID configured, not starting the proxy.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
log(`[PROXY] → [CLOUD] Not restarting, no new cloud connection, just cleaning up.`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.cleanStart = cleanStart;
|
|
107
|
+
class Context {
|
|
108
|
+
constructor(cloudSocket, master, masterPort, server, serverPort, uniqueId) {
|
|
109
|
+
this.deviceSocket = null;
|
|
110
|
+
this.master = master;
|
|
111
|
+
this.masterPort = masterPort;
|
|
112
|
+
this.cloudSocket = cloudSocket;
|
|
113
|
+
this.server = server;
|
|
114
|
+
this.serverPort = serverPort;
|
|
115
|
+
this.uniqueId = uniqueId;
|
|
116
|
+
this.setupCloudSocket();
|
|
117
|
+
}
|
|
118
|
+
setupCloudSocket() {
|
|
119
|
+
// set up handlers for the cloud socket
|
|
120
|
+
this.cloudSocket.on('message', message => {
|
|
121
|
+
this.handleDataFromCloud(message);
|
|
122
|
+
});
|
|
123
|
+
this.cloudSocket.on('close', (code, reason) => {
|
|
124
|
+
warning('[CLOUD] Connection closed: ' + code + ' ' + reason);
|
|
125
|
+
if (this.deviceSocket) {
|
|
126
|
+
this.deviceSocket.end();
|
|
127
|
+
this.deviceSocket = null;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
this.cloudSocket.on('error', err => {
|
|
131
|
+
error(`[CLOUD] Error: ${err.message}`);
|
|
132
|
+
if (this.deviceSocket) {
|
|
133
|
+
this.deviceSocket.end();
|
|
134
|
+
this.deviceSocket = null;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
handleDataFromCloud(data) {
|
|
139
|
+
debug(`[CLOUD] → [PROXY]: Data from Cloud: ${data.toString().slice(0, -1)}`);
|
|
140
|
+
if (!this.deviceSocket) {
|
|
141
|
+
// we either have a real client that wants to connect to the device,
|
|
142
|
+
// or we receive a heartbeat from the cloud server
|
|
143
|
+
if (this.isHeartbeatRequest(data)) {
|
|
144
|
+
debug(`[CLOUD] → [PROXY]: Received heartbeat from Cloud, returning response.`);
|
|
145
|
+
// respond to the heartbeat of the cloud server
|
|
146
|
+
this.cloudSocket.send("[72,3]"); // command([Command.HeartbeatStatus, CommandMethod.Heartbeat.server])
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// There is incomming data, it's a fresh connection, so: a new client wants to connect to the device
|
|
150
|
+
// 1) create a new (free) connection to the cloud server for the next client
|
|
151
|
+
// 2) connect to the device and send the initial data
|
|
152
|
+
log(`[PROXY] → [CLOUD] New client connection, creating new free connection for this proxy/device: ${this.uniqueId}.`);
|
|
153
|
+
makeNewCloudConnection(this.master, this.masterPort, this.server, this.serverPort, this.uniqueId);
|
|
154
|
+
this.makeDeviceConnection(this.master, this.masterPort, data);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else if (this.deviceSocket.readyState === 'open') {
|
|
158
|
+
this.deviceSocket.write(data);
|
|
159
|
+
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().slice(0, -1)}`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
warning(`[PROXY] → [DEVICE] Device socket is not open yet, waiting for connection... what to do with the data??`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
makeDeviceConnection(address, port, data) {
|
|
166
|
+
log(`[PROXY] → [DEVICE] Attempting to connect to local device at ${address}:${port}`);
|
|
167
|
+
if (!this.deviceSocket) {
|
|
168
|
+
this.deviceSocket = new net.Socket();
|
|
169
|
+
// Connect to local device and send the data that came in from the cloud
|
|
170
|
+
this.deviceSocket.connect(port, address, () => {
|
|
171
|
+
log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`);
|
|
172
|
+
this.setUpDeviceSocket();
|
|
173
|
+
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().slice(0, -1)}`);
|
|
174
|
+
this.deviceSocket.write(data);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
else if (this.deviceSocket.readyState === 'open') {
|
|
178
|
+
warning(`[PROXY] → [DEVICE] Device socket already open, sending data directly -- STRANGE !!`);
|
|
179
|
+
this.deviceSocket.write(data);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
error(`[PROXY] → [DEVICE] Device socket exists, but is not open yet !!`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
setUpDeviceSocket() {
|
|
186
|
+
this.deviceSocket.on('data', (data) => {
|
|
187
|
+
const message = data.toString('utf-8');
|
|
188
|
+
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.slice(0, -1)}`);
|
|
189
|
+
this.cloudSocket.send(message);
|
|
190
|
+
});
|
|
191
|
+
this.deviceSocket.on('close', () => {
|
|
192
|
+
log(`[DEVICE] → [PROXY] Device socket closed`);
|
|
193
|
+
this.removeConnection();
|
|
194
|
+
});
|
|
195
|
+
this.deviceSocket.on('error', (err) => {
|
|
196
|
+
error(`[DEVICE] → [PROXY] Device socket error: ${err.message}`);
|
|
197
|
+
this.removeConnection();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
removeConnection() {
|
|
201
|
+
this.deviceSocket = null;
|
|
202
|
+
// remove this context and close the cloud socket
|
|
203
|
+
const index = gCloudConnections.indexOf(this);
|
|
204
|
+
if (index !== -1) {
|
|
205
|
+
this.cloudSocket.close();
|
|
206
|
+
this.cloudSocket = null;
|
|
207
|
+
gCloudConnections.splice(index, 1);
|
|
208
|
+
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
209
|
+
}
|
|
210
|
+
if (gCloudConnections.length === 0) {
|
|
211
|
+
log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
|
|
212
|
+
// just to be sure: restart...
|
|
213
|
+
if (config.kind === "pm2") {
|
|
214
|
+
// PM2 should restart us.
|
|
215
|
+
log(`[PROXY] → [CLOUD] PM2 should restart us, exiting now.`);
|
|
216
|
+
process.exit();
|
|
217
|
+
}
|
|
218
|
+
else if (config.kind === "solo") {
|
|
219
|
+
log(`[PROXY] → [CLOUD] Running solo, restarting proxy.`);
|
|
220
|
+
this.restart();
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
log(`[PROXY] → [CLOUD] Running under gateway/homebridge, not restarting... not sure what to do here.`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
///////////////
|
|
228
|
+
// Utilities //
|
|
229
|
+
///////////////
|
|
230
|
+
restart() {
|
|
231
|
+
console.log('🔄 Restarting...');
|
|
232
|
+
(0, child_process_1.spawn)(process_1.execPath, process_1.argv.slice(1), {
|
|
233
|
+
cwd: (0, process_1.cwd)(),
|
|
234
|
+
detached: true,
|
|
235
|
+
stdio: 'inherit',
|
|
236
|
+
});
|
|
237
|
+
process.exit();
|
|
238
|
+
}
|
|
239
|
+
// check if it's a heartbeat request from the server
|
|
240
|
+
isHeartbeatRequest(data) {
|
|
241
|
+
// heartbeatKind = (poll = 1, poll_old = 2, serve = 3)
|
|
242
|
+
if (typeof data === 'string') {
|
|
243
|
+
return data === "[215,3]";
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
return ((data[0] === 91) && // [
|
|
247
|
+
(data[1] === 50) && // 2
|
|
248
|
+
(data[2] === 49) && // 1
|
|
249
|
+
(data[3] === 53) && // 5
|
|
250
|
+
(data[4] === 44) && // ,
|
|
251
|
+
(data[5] === 51) && // 3
|
|
252
|
+
(data[6] === 93)); // ]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
exports.Context = Context;
|
|
257
|
+
//# sourceMappingURL=proxy.js.map
|
package/server/proxy.ts
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
2
|
+
import * as WebSocket from 'ws';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import { argv, cwd, execPath } from 'process';
|
|
5
|
+
|
|
6
|
+
//
|
|
7
|
+
// [ device (TCP) ] ←→ [ Proxy = WebSocket client (ws) ] ←→ [ Cloud = 2 x WebSocket server (ws) ] ←→ [ browser client ]
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
export interface ProxyConfig {
|
|
11
|
+
cloudServer: string; // cloud server address
|
|
12
|
+
cloudPort: number; // cloud server port
|
|
13
|
+
masterAddress: string; // local master address
|
|
14
|
+
masterPort: number; // local master port
|
|
15
|
+
uniqueId: string; // unique ID for the device (for now the no-ip address + port)
|
|
16
|
+
debug?: boolean; // debug mode
|
|
17
|
+
kind?: "pm2" | "gw" | "solo";
|
|
18
|
+
}
|
|
19
|
+
export const kEmptyProxy: ProxyConfig = {
|
|
20
|
+
cloudServer: "ws.duotecno.eu",
|
|
21
|
+
cloudPort: 5098,
|
|
22
|
+
masterAddress: "",
|
|
23
|
+
masterPort: 5001,
|
|
24
|
+
uniqueId: "", // no unique ID -> don't start the proxy
|
|
25
|
+
debug: false, // default nodebug mode
|
|
26
|
+
kind: "solo" // default running under PM2 or other process manager -> don't restart yourself
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function setProxyConfig(c?: ProxyConfig): ProxyConfig {
|
|
30
|
+
// set the proxy config
|
|
31
|
+
if (c) {
|
|
32
|
+
config.cloudServer = c.cloudServer || kEmptyProxy.cloudServer;
|
|
33
|
+
config.cloudPort = c.cloudPort || kEmptyProxy.cloudPort;
|
|
34
|
+
config.masterAddress = c.masterAddress || kEmptyProxy.masterAddress;
|
|
35
|
+
config.masterPort = c.masterPort || kEmptyProxy.masterPort;
|
|
36
|
+
config.uniqueId = c.uniqueId || kEmptyProxy.uniqueId;
|
|
37
|
+
config.debug = !!c.debug;
|
|
38
|
+
config.kind = c.kind || kEmptyProxy.kind;
|
|
39
|
+
} else {
|
|
40
|
+
config = {...kEmptyProxy};
|
|
41
|
+
}
|
|
42
|
+
return config;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let config: ProxyConfig;
|
|
46
|
+
try {
|
|
47
|
+
setProxyConfig(require('../config-proxy.json'))
|
|
48
|
+
} catch (e) {
|
|
49
|
+
setProxyConfig();
|
|
50
|
+
console.log("No config-proxy.json found, using default values from homebridge or other.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const gCloudConnections: Array<Context> = [];
|
|
54
|
+
const kDebug = config.debug || false; // enable debug mode from config
|
|
55
|
+
|
|
56
|
+
/////////////
|
|
57
|
+
// Logging //
|
|
58
|
+
/////////////
|
|
59
|
+
function debug(str: string) {
|
|
60
|
+
if (kDebug) {
|
|
61
|
+
console.log(`DEBUG: ${str}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function warning(str: string) {
|
|
65
|
+
console.warn(`WARNING: ${str}`);
|
|
66
|
+
}
|
|
67
|
+
function log(str: string) {
|
|
68
|
+
console.log(`LOG: ${str}`);
|
|
69
|
+
}
|
|
70
|
+
function error(str: string) {
|
|
71
|
+
console.error(`ERROR: **** ${str} ****`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
///////////////////////////////
|
|
76
|
+
// Start up the proxy server //
|
|
77
|
+
///////////////////////////////
|
|
78
|
+
|
|
79
|
+
// if (config.kind != "gw") {
|
|
80
|
+
// makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
81
|
+
// }
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
export function makeNewCloudConnection(master: string, masterPort: number, server: string, serverPort: number, uniqueId: string) {
|
|
85
|
+
const cloudSocket = new WebSocket('ws://'+server+':'+serverPort+"/"+uniqueId);
|
|
86
|
+
|
|
87
|
+
cloudSocket.on('open', () => {
|
|
88
|
+
log(`[PROXY] → [CLOUD] Connected to Cloud at ${server}:${serverPort}`);
|
|
89
|
+
|
|
90
|
+
const context = new Context(cloudSocket, master, masterPort, server, serverPort, uniqueId);
|
|
91
|
+
gCloudConnections.push(context);
|
|
92
|
+
log(`[PROXY] → [CLOUD] New free connection #${gCloudConnections.length} to the Cloud at ${server}:${serverPort}`);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
log(`[PROXY] → [CLOUD] Attempting to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
////////////////
|
|
99
|
+
// Restarting //
|
|
100
|
+
////////////////
|
|
101
|
+
export function cleanStart(restart: boolean = false) {
|
|
102
|
+
if (gCloudConnections.length) {
|
|
103
|
+
gCloudConnections.forEach(context => {
|
|
104
|
+
if (context.deviceSocket) {
|
|
105
|
+
context.deviceSocket.end();
|
|
106
|
+
context.deviceSocket = null;
|
|
107
|
+
}
|
|
108
|
+
context.cloudSocket.close();
|
|
109
|
+
context.cloudSocket = null;
|
|
110
|
+
});
|
|
111
|
+
gCloudConnections.splice(0, gCloudConnections.length); // clear the array
|
|
112
|
+
log(`[PROXY] → [CLOUD] Cleaned up all cloud connections.`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (restart) {
|
|
116
|
+
if (config.uniqueId) {
|
|
117
|
+
makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
118
|
+
} else {
|
|
119
|
+
log(`[PROXY] → [CLOUD] Not restarting, no unique ID configured, not starting the proxy.`);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
log(`[PROXY] → [CLOUD] Not restarting, no new cloud connection, just cleaning up.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
export class Context {
|
|
128
|
+
deviceSocket: net.Socket;
|
|
129
|
+
master: string;
|
|
130
|
+
masterPort: number;
|
|
131
|
+
|
|
132
|
+
cloudSocket: WebSocket;
|
|
133
|
+
server: string;
|
|
134
|
+
serverPort: number;
|
|
135
|
+
|
|
136
|
+
// unique identifier for this device-gateway combination
|
|
137
|
+
uniqueId: string;
|
|
138
|
+
|
|
139
|
+
constructor(cloudSocket: WebSocket, master: string, masterPort: number, server: string, serverPort: number, uniqueId: string) {
|
|
140
|
+
this.deviceSocket = null;
|
|
141
|
+
this.master = master;
|
|
142
|
+
this.masterPort = masterPort;
|
|
143
|
+
|
|
144
|
+
this.cloudSocket = cloudSocket;
|
|
145
|
+
this.server = server;
|
|
146
|
+
this.serverPort = serverPort;
|
|
147
|
+
|
|
148
|
+
this.uniqueId = uniqueId;
|
|
149
|
+
|
|
150
|
+
this.setupCloudSocket();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
setupCloudSocket() {
|
|
154
|
+
// set up handlers for the cloud socket
|
|
155
|
+
this.cloudSocket.on('message', message => {
|
|
156
|
+
this.handleDataFromCloud(message as Buffer);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
this.cloudSocket.on('close', (code, reason) => {
|
|
160
|
+
warning('[CLOUD] Connection closed: ' + code + ' ' + reason);
|
|
161
|
+
if (this.deviceSocket) {
|
|
162
|
+
this.deviceSocket.end();
|
|
163
|
+
this.deviceSocket = null;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
this.cloudSocket.on('error', err => {
|
|
168
|
+
error(`[CLOUD] Error: ${err.message}`);
|
|
169
|
+
if (this.deviceSocket) {
|
|
170
|
+
this.deviceSocket.end();
|
|
171
|
+
this.deviceSocket = null;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
handleDataFromCloud(data: Buffer) {
|
|
177
|
+
debug(`[CLOUD] → [PROXY]: Data from Cloud: ${data.toString().slice(0, -1)}`);
|
|
178
|
+
|
|
179
|
+
if (!this.deviceSocket) {
|
|
180
|
+
// we either have a real client that wants to connect to the device,
|
|
181
|
+
// or we receive a heartbeat from the cloud server
|
|
182
|
+
|
|
183
|
+
if (this.isHeartbeatRequest(data)) {
|
|
184
|
+
debug(`[CLOUD] → [PROXY]: Received heartbeat from Cloud, returning response.`);
|
|
185
|
+
// respond to the heartbeat of the cloud server
|
|
186
|
+
this.cloudSocket.send("[72,3]"); // command([Command.HeartbeatStatus, CommandMethod.Heartbeat.server])
|
|
187
|
+
|
|
188
|
+
} else {
|
|
189
|
+
// There is incomming data, it's a fresh connection, so: a new client wants to connect to the device
|
|
190
|
+
// 1) create a new (free) connection to the cloud server for the next client
|
|
191
|
+
// 2) connect to the device and send the initial data
|
|
192
|
+
log(`[PROXY] → [CLOUD] New client connection, creating new free connection for this proxy/device: ${this.uniqueId}.`);
|
|
193
|
+
makeNewCloudConnection(this.master, this.masterPort, this.server, this.serverPort, this.uniqueId);
|
|
194
|
+
this.makeDeviceConnection(this.master, this.masterPort, data);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
} else if (this.deviceSocket.readyState === 'open') {
|
|
198
|
+
this.deviceSocket.write(data);
|
|
199
|
+
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().slice(0, -1)}`);
|
|
200
|
+
|
|
201
|
+
} else {
|
|
202
|
+
warning(`[PROXY] → [DEVICE] Device socket is not open yet, waiting for connection... what to do with the data??`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
makeDeviceConnection(address: string, port: number, data: Buffer) {
|
|
208
|
+
log(`[PROXY] → [DEVICE] Attempting to connect to local device at ${address}:${port}`);
|
|
209
|
+
|
|
210
|
+
if (!this.deviceSocket) {
|
|
211
|
+
this.deviceSocket = new net.Socket();
|
|
212
|
+
|
|
213
|
+
// Connect to local device and send the data that came in from the cloud
|
|
214
|
+
this.deviceSocket.connect(port, address, () => {
|
|
215
|
+
log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`);
|
|
216
|
+
|
|
217
|
+
this.setUpDeviceSocket();
|
|
218
|
+
|
|
219
|
+
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().slice(0, -1)}`);
|
|
220
|
+
this.deviceSocket.write(data);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
} else if (this.deviceSocket.readyState === 'open') {
|
|
224
|
+
warning(`[PROXY] → [DEVICE] Device socket already open, sending data directly -- STRANGE !!`);
|
|
225
|
+
this.deviceSocket.write(data);
|
|
226
|
+
|
|
227
|
+
} else {
|
|
228
|
+
error(`[PROXY] → [DEVICE] Device socket exists, but is not open yet !!`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
setUpDeviceSocket() {
|
|
233
|
+
this.deviceSocket.on('data', (data: Buffer) => {
|
|
234
|
+
const message = data.toString('utf-8');
|
|
235
|
+
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.slice(0, -1)}`);
|
|
236
|
+
this.cloudSocket.send(message);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
this.deviceSocket.on('close', () => {
|
|
240
|
+
log(`[DEVICE] → [PROXY] Device socket closed`);
|
|
241
|
+
this.removeConnection()
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
this.deviceSocket.on('error', (err) => {
|
|
245
|
+
error(`[DEVICE] → [PROXY] Device socket error: ${err.message}`);
|
|
246
|
+
this.removeConnection()
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
removeConnection() {
|
|
251
|
+
this.deviceSocket = null;
|
|
252
|
+
|
|
253
|
+
// remove this context and close the cloud socket
|
|
254
|
+
const index = gCloudConnections.indexOf(this);
|
|
255
|
+
if (index !== -1) {
|
|
256
|
+
this.cloudSocket.close();
|
|
257
|
+
this.cloudSocket = null;
|
|
258
|
+
gCloudConnections.splice(index, 1);
|
|
259
|
+
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (gCloudConnections.length === 0) {
|
|
263
|
+
log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
|
|
264
|
+
// just to be sure: restart...
|
|
265
|
+
|
|
266
|
+
if (config.kind === "pm2") {
|
|
267
|
+
// PM2 should restart us.
|
|
268
|
+
log(`[PROXY] → [CLOUD] PM2 should restart us, exiting now.`);
|
|
269
|
+
process.exit();
|
|
270
|
+
|
|
271
|
+
} else if (config.kind === "solo"){
|
|
272
|
+
log(`[PROXY] → [CLOUD] Running solo, restarting proxy.`);
|
|
273
|
+
this.restart();
|
|
274
|
+
|
|
275
|
+
} else {
|
|
276
|
+
log(`[PROXY] → [CLOUD] Running under gateway/homebridge, not restarting... not sure what to do here.`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
///////////////
|
|
283
|
+
// Utilities //
|
|
284
|
+
///////////////
|
|
285
|
+
|
|
286
|
+
restart() {
|
|
287
|
+
console.log('🔄 Restarting...');
|
|
288
|
+
|
|
289
|
+
spawn(execPath, argv.slice(1), {
|
|
290
|
+
cwd: cwd(),
|
|
291
|
+
detached: true,
|
|
292
|
+
stdio: 'inherit',
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
process.exit();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// check if it's a heartbeat request from the server
|
|
299
|
+
isHeartbeatRequest(data: Buffer): boolean {
|
|
300
|
+
// heartbeatKind = (poll = 1, poll_old = 2, serve = 3)
|
|
301
|
+
|
|
302
|
+
if (typeof data === 'string') {
|
|
303
|
+
return data === "[215,3]";
|
|
304
|
+
|
|
305
|
+
} else {
|
|
306
|
+
return ((data[0] === 91) && // [
|
|
307
|
+
(data[1] === 50) && // 2
|
|
308
|
+
(data[2] === 49) && // 1
|
|
309
|
+
(data[3] === 53) && // 5
|
|
310
|
+
(data[4] === 44) && // ,
|
|
311
|
+
(data[5] === 51) && // 3
|
|
312
|
+
(data[6] === 93)); // ]
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
package/server/smartapp.js
CHANGED
|
@@ -28,6 +28,7 @@ const fs_1 = require("fs");
|
|
|
28
28
|
const config_1 = require("../duotecno/config");
|
|
29
29
|
const HB_1 = require("./HB");
|
|
30
30
|
const HA_API_1 = require("./HA-API");
|
|
31
|
+
const proxy_1 = require("./proxy");
|
|
31
32
|
const kMaster = { name: "master", type: "string", default: "0.0.0.0:5001" };
|
|
32
33
|
const kAddress = { name: "address", type: "string", default: "0.0.0.0" };
|
|
33
34
|
const kAddresses = { name: "addresses", type: "string", default: "[0.0.0.0]" };
|
|
@@ -81,6 +82,7 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
81
82
|
this.addFile("materializeCSS", __dirname + "/views/assets/materialize.min.css", "text/css");
|
|
82
83
|
this.addFile("materializeJS", __dirname + "/views/assets/materialize.min.js", "text/javascript");
|
|
83
84
|
this.addFile("favicon", __dirname + "/views/assets/favicon.ico", "image/x-icon");
|
|
85
|
+
this.addFile("proxy", __dirname + "/views/proxy.ejs", "text/html");
|
|
84
86
|
}
|
|
85
87
|
writeConfig() {
|
|
86
88
|
// copy switches into config, eliminate the runtime stuff (like unit)
|
|
@@ -185,6 +187,9 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
185
187
|
else if (context.request === "settings") {
|
|
186
188
|
return this.doSettings(context);
|
|
187
189
|
}
|
|
190
|
+
else if (context.request === "proxy") {
|
|
191
|
+
return this.doProxy(context);
|
|
192
|
+
}
|
|
188
193
|
else {
|
|
189
194
|
return _super.doRequest.call(this, context);
|
|
190
195
|
}
|
|
@@ -225,6 +230,31 @@ class SmartApp extends webapp_1.WebApp {
|
|
|
225
230
|
return { name, value, masterAddress: context["masterAddress"], masterPort: context["masterPort"],
|
|
226
231
|
logicalAddress, logicalNodeAddress };
|
|
227
232
|
}
|
|
233
|
+
///////////
|
|
234
|
+
// Proxy //
|
|
235
|
+
///////////
|
|
236
|
+
doProxy(context) {
|
|
237
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
238
|
+
let config = this.config.proxy || proxy_1.kEmptyProxy;
|
|
239
|
+
if (context.action === "save") {
|
|
240
|
+
// save the proxy config
|
|
241
|
+
config.cloudServer = context.getParam({ name: "cloudServer", type: "string" });
|
|
242
|
+
config.cloudPort = context.getParam({ name: "cloudPort", type: "integer" });
|
|
243
|
+
config.masterAddress = context.getParam({ name: "masterAddress", type: "string" });
|
|
244
|
+
config.masterPort = context.getParam({ name: "masterPort", type: "integer" });
|
|
245
|
+
config.uniqueId = context.getParam({ name: "uniqueId", type: "string" });
|
|
246
|
+
this.write("proxy", config);
|
|
247
|
+
(0, proxy_1.setProxyConfig)(config);
|
|
248
|
+
}
|
|
249
|
+
else if (context.action === "restart") {
|
|
250
|
+
(0, proxy_1.cleanStart)(true);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
// just show the proxy config
|
|
254
|
+
}
|
|
255
|
+
return this.ejs("proxy", context, { config });
|
|
256
|
+
});
|
|
257
|
+
}
|
|
228
258
|
//////////////
|
|
229
259
|
// Settings //
|
|
230
260
|
//////////////
|
package/server/smartapp.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { setToken } from "../duotecno/config";
|
|
|
24
24
|
import { PowerBase } from "./powerbase";
|
|
25
25
|
import { dt2hbvalue, hb2dtvalue, hbAccessories, hbGetValue, hbLogin, hbName, hbService, hbSetState, hbSubscribe } from "./HB";
|
|
26
26
|
import { httpRequest, ContentType } from "./HA-API";
|
|
27
|
+
import { cleanStart, kEmptyProxy, ProxyConfig, setProxyConfig } from "./proxy";
|
|
27
28
|
|
|
28
29
|
const kMaster = {name: "master", type: "string", default: "0.0.0.0:5001"} as const;
|
|
29
30
|
const kAddress = {name: "address", type: "string", default: "0.0.0.0"} as const;
|
|
@@ -59,6 +60,7 @@ export class SmartApp extends WebApp {
|
|
|
59
60
|
platform: Platform;
|
|
60
61
|
switches: Array<Switch>;
|
|
61
62
|
links: Array<Link>;
|
|
63
|
+
proxy: ProxyConfig;
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
constructor(system: System, power: Power, platform: Platform) {
|
|
@@ -100,6 +102,8 @@ export class SmartApp extends WebApp {
|
|
|
100
102
|
this.addFile("materializeCSS", __dirname + "/views/assets/materialize.min.css", "text/css");
|
|
101
103
|
this.addFile("materializeJS", __dirname + "/views/assets/materialize.min.js", "text/javascript");
|
|
102
104
|
this.addFile("favicon", __dirname + "/views/assets/favicon.ico", "image/x-icon");
|
|
105
|
+
|
|
106
|
+
this.addFile("proxy", __dirname + "/views/proxy.ejs", "text/html");
|
|
103
107
|
}
|
|
104
108
|
|
|
105
109
|
writeConfig() {
|
|
@@ -212,6 +216,9 @@ export class SmartApp extends WebApp {
|
|
|
212
216
|
} else if (context.request === "settings") {
|
|
213
217
|
return this.doSettings(context);
|
|
214
218
|
|
|
219
|
+
} else if (context.request === "proxy") {
|
|
220
|
+
return this.doProxy(context);
|
|
221
|
+
|
|
215
222
|
} else {
|
|
216
223
|
return super.doRequest(context);
|
|
217
224
|
}
|
|
@@ -254,6 +261,31 @@ export class SmartApp extends WebApp {
|
|
|
254
261
|
logicalAddress, logicalNodeAddress };
|
|
255
262
|
}
|
|
256
263
|
|
|
264
|
+
///////////
|
|
265
|
+
// Proxy //
|
|
266
|
+
///////////
|
|
267
|
+
async doProxy(context: Context): Promise<HttpResponse> {
|
|
268
|
+
let config: ProxyConfig = this.config.proxy || kEmptyProxy;
|
|
269
|
+
|
|
270
|
+
if (context.action === "save") {
|
|
271
|
+
// save the proxy config
|
|
272
|
+
config.cloudServer = context.getParam({name: "cloudServer", type: "string"});
|
|
273
|
+
config.cloudPort = context.getParam({name: "cloudPort", type: "integer"});
|
|
274
|
+
config.masterAddress = context.getParam({name: "masterAddress", type: "string"});
|
|
275
|
+
config.masterPort = context.getParam({name: "masterPort", type: "integer"});
|
|
276
|
+
config.uniqueId = context.getParam({name: "uniqueId", type: "string"});
|
|
277
|
+
this.write("proxy", config);
|
|
278
|
+
setProxyConfig(config);
|
|
279
|
+
|
|
280
|
+
} else if (context.action === "restart") {
|
|
281
|
+
cleanStart(true);
|
|
282
|
+
|
|
283
|
+
} else {
|
|
284
|
+
// just show the proxy config
|
|
285
|
+
}
|
|
286
|
+
return this.ejs("proxy", context, { config });
|
|
287
|
+
}
|
|
288
|
+
|
|
257
289
|
//////////////
|
|
258
290
|
// Settings //
|
|
259
291
|
//////////////
|
package/server/views/nav.ejs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<ul id="nav-mobile" class="right">
|
|
5
5
|
<li <%= (request === "masters") ? 'class="active"' : '' %></li><a href="/masters">Masters</a></li>
|
|
6
6
|
<li <%= (request === "services") ? 'class="active"' : '' %></li><a href="/services">Services</a></li>
|
|
7
|
+
<li <%= (request === "proxy") ? 'class="active"' : '' %></li><a href="/proxy">Proxy</a></li>
|
|
7
8
|
<li <%= (request === "links") ? 'class="active"' : '' %></li><a href="/links">Links</a></li>
|
|
8
9
|
<% if (hasSmappee) { %><li <%= ((request === "power") && (type === "smappee")) ? 'class="active"' : '' %></li><a href="/power?type=smappee">Smappee</a></li><% } %>
|
|
9
10
|
<% if (hasP1) { %><li <%= ((request === "power") && (type === "p1")) ? 'class="active"' : '' %></li><a href="/power?type=p1">P1</a></li><% } %>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<link type="text/css" rel="stylesheet" href="/files/min.css" media="screen,projection"/>
|
|
5
|
+
|
|
6
|
+
<!--Let browser know website is optimized for mobile-->
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
8
|
+
<link rel="shortcut icon" href="/files/favicon.ico" type="image/x-icon" />
|
|
9
|
+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
10
|
+
|
|
11
|
+
<%- include('/views/style'); -%>
|
|
12
|
+
</head>
|
|
13
|
+
|
|
14
|
+
<body>
|
|
15
|
+
<%- include('/views/nav', this); -%>
|
|
16
|
+
|
|
17
|
+
<form method="POST" action="/proxy">
|
|
18
|
+
<input type="hidden" name="daction" value="save">
|
|
19
|
+
<h1>
|
|
20
|
+
Proxy Config
|
|
21
|
+
<button class="right btn waves-effect waves-light" type="submit" name="action" value="cancel">Back</button>
|
|
22
|
+
<button class="right btn waves-effect waves-light" type="submit" name="action" value="save">Save</button>
|
|
23
|
+
<button class="right btn waves-effect waves-light red" type="submit" name="action" value="restart">Restart</button>
|
|
24
|
+
</h1>
|
|
25
|
+
|
|
26
|
+
<div class="row">
|
|
27
|
+
<div class="input-field col s12">
|
|
28
|
+
<input placeholder="ws.duotecno.eu" id="cloudServer" name="cloudServer" type="text"
|
|
29
|
+
value="<%= config.cloudServer %>">
|
|
30
|
+
<label for="name">CloudServer</label>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="row">
|
|
34
|
+
<div class="input-field col s12">
|
|
35
|
+
<input placeholder="5099" id="cloudPort" name="cloudPort" type="number"
|
|
36
|
+
value="<%= config.cloudPort %>">
|
|
37
|
+
<label for="name">CloudPort</label>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="row">
|
|
42
|
+
<div class="input-field col s12">
|
|
43
|
+
<input placeholder="192.168.0.2" id="masterAddress" name="masterAddress" type="text"
|
|
44
|
+
value="<%= config.masterAddress %>">
|
|
45
|
+
<label for="name">MasterAddress</label>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="row">
|
|
50
|
+
<div class="input-field col s12">
|
|
51
|
+
<input placeholder="5001" id="masterPort" name="masterPort" type="text"
|
|
52
|
+
value="<%= config.masterPort %>">
|
|
53
|
+
<label for="name">MasterPort</label>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="row">
|
|
58
|
+
<div class="input-field col s12">
|
|
59
|
+
<input placeholder="client:5002" id="uniqueId" name="uniqueId" type="text"
|
|
60
|
+
value="<%= config.uniqueId %>">
|
|
61
|
+
<label for="name">UniqueId</label>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
</form>
|
|
66
|
+
<%- include('/views/footer'); -%>
|
|
67
|
+
|
|
68
|
+
</body>
|
|
69
|
+
|
|
70
|
+
<%- include('/views/scripts'); -%>
|
|
71
|
+
</html>
|