homebridge-smartsystem 6.8.4 → 6.8.7
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 +4 -0
- package/package.json +2 -2
- package/server/proxy.js +114 -51
- package/server/proxy.ts +142 -62
package/README.md
CHANGED
|
@@ -307,6 +307,10 @@ Homebridge UI version
|
|
|
307
307
|
- 1: max/min values for hbValues
|
|
308
308
|
- 2: HB up/down -> don't respond to motor unit's, only macro
|
|
309
309
|
- 3: remember request before the login kicked in (and redirect to that one, once the login was succesful)
|
|
310
|
+
- 4: now using only TCP sockets to the server
|
|
311
|
+
- 5: making it more robust / inline with repo-service version
|
|
312
|
+
- 6: removed double error handlers on cloudSocket
|
|
313
|
+
- 7: restart if no free connections
|
|
310
314
|
|
|
311
315
|
## Hardware
|
|
312
316
|
A Raspberry Pi that connects to a Duotecno IP Node
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-smartsystem",
|
|
3
3
|
"displayname": "Duotecno Bridge",
|
|
4
|
-
"version": "6.8.
|
|
5
|
-
"description": "SmartServer (Proxy
|
|
4
|
+
"version": "6.8.7",
|
|
5
|
+
"description": "SmartServer (Proxy TCP sockets to the cloud, Smappee MQTT, Duotecno IP Nodes, Homekit interface)",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"author": "Johan Coppieters",
|
|
8
8
|
"homepage": "http://www.duotecno.be",
|
package/server/proxy.js
CHANGED
|
@@ -38,6 +38,7 @@ catch (e) {
|
|
|
38
38
|
setProxyConfig();
|
|
39
39
|
console.log("No config-proxy.json found, using default values from homebridge or other.");
|
|
40
40
|
}
|
|
41
|
+
let connectionChecker = null;
|
|
41
42
|
const gCloudConnections = [];
|
|
42
43
|
const kDebug = config.debug || false; // enable debug mode from config
|
|
43
44
|
/////////////
|
|
@@ -60,13 +61,23 @@ function error(str) {
|
|
|
60
61
|
///////////////////////////////
|
|
61
62
|
// Start up the proxy server //
|
|
62
63
|
///////////////////////////////
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
function
|
|
64
|
+
cleanStart(true);
|
|
65
|
+
function makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId, count = 0) {
|
|
66
|
+
// retry making a cloud connection
|
|
67
|
+
function reconnect(err) {
|
|
68
|
+
error(`[PROXY] → [CLOUD] Failed to connect ${count + 1} times to the Cloud server: ${err.message}`);
|
|
69
|
+
if (count < 3) {
|
|
70
|
+
setTimeout(() => {
|
|
71
|
+
makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId, count + 1);
|
|
72
|
+
}, 1000);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
67
75
|
const cloudSocket = new net.Socket();
|
|
76
|
+
cloudSocket.once('error', reconnect);
|
|
77
|
+
log(`[PROXY] → [CLOUD] Attempt ${count + 1} to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
|
|
68
78
|
cloudSocket.connect(serverPort, server, () => {
|
|
69
79
|
log(`[PROXY] → [CLOUD] Connected to Cloud at ${server}:${serverPort}`);
|
|
80
|
+
cloudSocket.removeListener('error', reconnect);
|
|
70
81
|
// Send unique ID as the first message to identify this proxy/device
|
|
71
82
|
cloudSocket.write(`[${uniqueId}]`);
|
|
72
83
|
debug(`[PROXY] → [CLOUD] Sent unique ID: ${uniqueId}`);
|
|
@@ -74,10 +85,6 @@ function makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId
|
|
|
74
85
|
gCloudConnections.push(context);
|
|
75
86
|
log(`[PROXY] → [CLOUD] New free connection #${gCloudConnections.length} to the Cloud at ${server}:${serverPort}`);
|
|
76
87
|
});
|
|
77
|
-
cloudSocket.on('error', (err) => {
|
|
78
|
-
error(`[PROXY] → [CLOUD] Failed to connect to Cloud: ${err.message}`);
|
|
79
|
-
});
|
|
80
|
-
log(`[PROXY] → [CLOUD] Attempting to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
|
|
81
88
|
}
|
|
82
89
|
exports.makeNewCloudConnection = makeNewCloudConnection;
|
|
83
90
|
////////////////
|
|
@@ -85,20 +92,25 @@ exports.makeNewCloudConnection = makeNewCloudConnection;
|
|
|
85
92
|
////////////////
|
|
86
93
|
function cleanStart(restart = false) {
|
|
87
94
|
if (gCloudConnections.length) {
|
|
88
|
-
gCloudConnections.forEach(
|
|
89
|
-
|
|
90
|
-
context.deviceSocket.end();
|
|
91
|
-
context.deviceSocket = null;
|
|
92
|
-
}
|
|
93
|
-
context.cloudSocket.end();
|
|
94
|
-
context.cloudSocket = null;
|
|
95
|
-
});
|
|
95
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
96
|
+
log(`[PROXY] → [CLOUD] Cleaned up all ${gCloudConnections.length} cloud connections.`);
|
|
96
97
|
gCloudConnections.splice(0, gCloudConnections.length); // clear the array
|
|
97
|
-
|
|
98
|
+
}
|
|
99
|
+
if (connectionChecker) {
|
|
100
|
+
clearInterval(connectionChecker);
|
|
101
|
+
connectionChecker = null;
|
|
98
102
|
}
|
|
99
103
|
if (restart) {
|
|
100
104
|
if (config.uniqueId) {
|
|
101
105
|
makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
106
|
+
// check every 5 second for a free connection
|
|
107
|
+
connectionChecker = setInterval(() => {
|
|
108
|
+
const freeConnection = gCloudConnections.find((ctx) => (!ctx.deviceSocket) && ctx.cloudSocket && (ctx.cloudSocket.readyState === 'open'));
|
|
109
|
+
if (!freeConnection) {
|
|
110
|
+
error(`[PROXY] → [CLOUD] No free connections available, restarting proxy. ***************************`);
|
|
111
|
+
cleanStart(true);
|
|
112
|
+
}
|
|
113
|
+
}, 5000);
|
|
102
114
|
}
|
|
103
115
|
else {
|
|
104
116
|
log(`[PROXY] → [CLOUD] Not restarting, no unique ID configured, not starting the proxy.`);
|
|
@@ -122,7 +134,7 @@ class Context {
|
|
|
122
134
|
}
|
|
123
135
|
setupCloudSocket() {
|
|
124
136
|
// set up handlers for the cloud socket
|
|
125
|
-
this.cloudSocket.on('data', data => {
|
|
137
|
+
this.cloudSocket.on('data', (data) => {
|
|
126
138
|
this.handleDataFromCloud(data);
|
|
127
139
|
});
|
|
128
140
|
this.cloudSocket.on('close', () => {
|
|
@@ -165,8 +177,8 @@ class Context {
|
|
|
165
177
|
}
|
|
166
178
|
}
|
|
167
179
|
else if (this.deviceSocket.readyState === 'open') {
|
|
168
|
-
this.deviceSocket.write(
|
|
169
|
-
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().
|
|
180
|
+
this.deviceSocket.write(data);
|
|
181
|
+
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().trim()}`);
|
|
170
182
|
}
|
|
171
183
|
else {
|
|
172
184
|
warning(`[PROXY] → [DEVICE] Device socket is not open yet, waiting for connection... what to do with the data??`);
|
|
@@ -185,13 +197,13 @@ class Context {
|
|
|
185
197
|
}
|
|
186
198
|
log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`);
|
|
187
199
|
this.setUpDeviceSocket();
|
|
188
|
-
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().
|
|
189
|
-
this.deviceSocket.write(
|
|
200
|
+
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().trim()}`);
|
|
201
|
+
this.deviceSocket.write(data);
|
|
190
202
|
});
|
|
191
203
|
}
|
|
192
204
|
else if (this.deviceSocket.readyState === 'open') {
|
|
193
205
|
warning(`[PROXY] → [DEVICE] Device socket already open, sending data directly -- STRANGE !!`);
|
|
194
|
-
this.deviceSocket.write(
|
|
206
|
+
this.deviceSocket.write(data);
|
|
195
207
|
}
|
|
196
208
|
else {
|
|
197
209
|
error(`[PROXY] → [DEVICE] Device socket exists, but is not open yet !!`);
|
|
@@ -204,8 +216,8 @@ class Context {
|
|
|
204
216
|
}
|
|
205
217
|
this.deviceSocket.on('data', (data) => {
|
|
206
218
|
const message = data.toString('utf-8');
|
|
207
|
-
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.
|
|
208
|
-
this.cloudSocket.write(
|
|
219
|
+
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.trim()}`);
|
|
220
|
+
this.cloudSocket.write(data);
|
|
209
221
|
});
|
|
210
222
|
this.deviceSocket.on('close', () => {
|
|
211
223
|
log(`[DEVICE] → [PROXY] Device socket closed`);
|
|
@@ -216,33 +228,43 @@ class Context {
|
|
|
216
228
|
this.removeConnection();
|
|
217
229
|
});
|
|
218
230
|
}
|
|
219
|
-
|
|
220
|
-
// Clean up device socket
|
|
231
|
+
cleanupSockets() {
|
|
232
|
+
// Clean up the device socket
|
|
221
233
|
if (this.deviceSocket) {
|
|
222
234
|
if (!this.deviceSocket.destroyed) {
|
|
235
|
+
this.deviceSocket.removeAllListeners();
|
|
223
236
|
this.deviceSocket.end();
|
|
237
|
+
this.deviceSocket.destroy();
|
|
224
238
|
}
|
|
225
239
|
this.deviceSocket = null;
|
|
226
240
|
}
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
241
|
+
// Clean up the cloud socket
|
|
242
|
+
if (this.cloudSocket) {
|
|
243
|
+
if (!this.cloudSocket.destroyed) {
|
|
244
|
+
this.cloudSocket.removeAllListeners();
|
|
245
|
+
this.cloudSocket.end();
|
|
246
|
+
this.cloudSocket.destroy();
|
|
247
|
+
}
|
|
231
248
|
this.cloudSocket = null;
|
|
232
|
-
gCloudConnections.splice(index, 1);
|
|
233
|
-
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
234
249
|
}
|
|
250
|
+
}
|
|
251
|
+
removeConnection() {
|
|
252
|
+
const index = gCloudConnections.indexOf(this);
|
|
253
|
+
if (index !== -1)
|
|
254
|
+
gCloudConnections.splice(index, 1);
|
|
255
|
+
this.cleanupSockets();
|
|
256
|
+
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
235
257
|
if (gCloudConnections.length === 0) {
|
|
236
258
|
log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
|
|
237
259
|
// just to be sure: restart...
|
|
238
260
|
if (config.kind === "pm2") {
|
|
239
261
|
// PM2 should restart us.
|
|
240
262
|
log(`[PROXY] → [CLOUD] PM2 should restart us, exiting now.`);
|
|
241
|
-
process.exit();
|
|
263
|
+
setTimeout(() => process.exit(0), 100);
|
|
242
264
|
}
|
|
243
265
|
else if (config.kind === "solo") {
|
|
244
266
|
log(`[PROXY] → [CLOUD] Running solo, restarting proxy.`);
|
|
245
|
-
|
|
267
|
+
restart();
|
|
246
268
|
}
|
|
247
269
|
else {
|
|
248
270
|
log(`[PROXY] → [CLOUD] Running under gateway/homebridge, not restarting... not sure what to do here.`);
|
|
@@ -252,25 +274,18 @@ class Context {
|
|
|
252
274
|
///////////////
|
|
253
275
|
// Utilities //
|
|
254
276
|
///////////////
|
|
255
|
-
restart() {
|
|
256
|
-
console.log('🔄 Restarting...');
|
|
257
|
-
(0, child_process_1.spawn)(process_1.execPath, process_1.argv.slice(1), {
|
|
258
|
-
cwd: (0, process_1.cwd)(),
|
|
259
|
-
detached: true,
|
|
260
|
-
stdio: 'inherit',
|
|
261
|
-
});
|
|
262
|
-
process.exit();
|
|
263
|
-
}
|
|
264
277
|
// check if it's a heartbeat request from the server
|
|
265
278
|
isHeartbeatRequest(data) {
|
|
279
|
+
const msg = data.toString('utf-8').trim();
|
|
280
|
+
return msg === '[215,3]';
|
|
266
281
|
// hearbeatKind = (poll = 1, poll_old = 2, serve = 3)
|
|
267
|
-
return ((data[0] === 91) &&
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
282
|
+
// return ((data[0] === 91) && // [
|
|
283
|
+
// (data[1] === 50) && // 2
|
|
284
|
+
// (data[2] === 49) && // 1
|
|
285
|
+
// (data[3] === 53) && // 5
|
|
286
|
+
// (data[4] === 44) && // ,
|
|
287
|
+
// (data[5] === 51) && // 3
|
|
288
|
+
// (data[6] === 93)); // ]
|
|
274
289
|
}
|
|
275
290
|
// check if it's a server response after connecting and sending our uniqueID
|
|
276
291
|
isConnectionResponse(data) {
|
|
@@ -280,4 +295,52 @@ class Context {
|
|
|
280
295
|
}
|
|
281
296
|
}
|
|
282
297
|
exports.Context = Context;
|
|
298
|
+
function restart() {
|
|
299
|
+
console.log('🔄 Restarting...');
|
|
300
|
+
setTimeout(() => {
|
|
301
|
+
(0, child_process_1.spawn)(process_1.execPath, process_1.argv.slice(1), {
|
|
302
|
+
cwd: (0, process_1.cwd)(),
|
|
303
|
+
detached: true,
|
|
304
|
+
stdio: 'inherit',
|
|
305
|
+
});
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}, 100);
|
|
308
|
+
}
|
|
309
|
+
// Graceful shutdown handler
|
|
310
|
+
function handleShutdown(signal) {
|
|
311
|
+
log(`[PROXY] → [SYSTEM] Received ${signal}, shutting down gracefully...`);
|
|
312
|
+
if (connectionChecker)
|
|
313
|
+
clearInterval(connectionChecker);
|
|
314
|
+
// Close all cloud connections
|
|
315
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
316
|
+
gCloudConnections.splice(0, gCloudConnections.length);
|
|
317
|
+
log(`[PROXY] → [SYSTEM] All connections closed. Exiting.`);
|
|
318
|
+
if (config.kind === "solo") {
|
|
319
|
+
log(`[PROXY] → [SYSTEM] Restarting proxy in solo mode...`);
|
|
320
|
+
restart();
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
log(`[PROXY] → [SYSTEM] Exiting without restart.`);
|
|
324
|
+
setTimeout(() => process.exit(0), 100);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Listen for termination signals
|
|
328
|
+
process.on('SIGINT', () => handleShutdown('SIGINT'));
|
|
329
|
+
process.on('SIGTERM', () => handleShutdown('SIGTERM'));
|
|
330
|
+
process.on('SIGINT', (err) => {
|
|
331
|
+
error(`[SYSTEM] received SIGINT`);
|
|
332
|
+
handleShutdown('SIGINT');
|
|
333
|
+
});
|
|
334
|
+
process.on('SIGTERM', (err) => {
|
|
335
|
+
error(`[SYSTEM] received SIGTERM`);
|
|
336
|
+
handleShutdown('SIGTERM');
|
|
337
|
+
});
|
|
338
|
+
process.on('uncaughtException', (err) => {
|
|
339
|
+
error(`[SYSTEM] Uncaught Exception: ${err.message}`);
|
|
340
|
+
handleShutdown('uncaughtException');
|
|
341
|
+
});
|
|
342
|
+
process.on('unhandledRejection', (reason) => {
|
|
343
|
+
error(`[SYSTEM] Unhandled Rejection: ${reason}`);
|
|
344
|
+
handleShutdown('unhandledRejection');
|
|
345
|
+
});
|
|
283
346
|
//# sourceMappingURL=proxy.js.map
|
package/server/proxy.ts
CHANGED
|
@@ -44,13 +44,14 @@ export function setProxyConfig(c?: ProxyConfig): ProxyConfig {
|
|
|
44
44
|
|
|
45
45
|
let config: ProxyConfig;
|
|
46
46
|
try {
|
|
47
|
-
setProxyConfig(require('../config-proxy.json'))
|
|
47
|
+
setProxyConfig(require('../config-proxy.json'));
|
|
48
48
|
} catch (e) {
|
|
49
49
|
setProxyConfig();
|
|
50
50
|
console.log("No config-proxy.json found, using default values from homebridge or other.");
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
let connectionChecker: NodeJS.Timer | null = null;
|
|
54
55
|
|
|
55
56
|
const gCloudConnections: Array<Context> = [];
|
|
56
57
|
const kDebug = config.debug || false; // enable debug mode from config
|
|
@@ -77,16 +78,28 @@ function error(str: string) {
|
|
|
77
78
|
///////////////////////////////
|
|
78
79
|
// Start up the proxy server //
|
|
79
80
|
///////////////////////////////
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
81
|
+
cleanStart(true);
|
|
82
|
+
|
|
83
83
|
|
|
84
|
+
export function makeNewCloudConnection(master: string, masterPort: number, server: string, serverPort: number, uniqueId: string, count = 0) {
|
|
85
|
+
// retry making a cloud connection
|
|
86
|
+
function reconnect(err: Error) {
|
|
87
|
+
error(`[PROXY] → [CLOUD] Failed to connect ${count+1} times to the Cloud server: ${err.message}`);
|
|
88
|
+
if (count < 3) {
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId, count + 1);
|
|
91
|
+
}, 1000);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
84
94
|
|
|
85
|
-
export function makeNewCloudConnection(master: string, masterPort: number, server: string, serverPort: number, uniqueId: string) {
|
|
86
95
|
const cloudSocket = new net.Socket();
|
|
87
|
-
|
|
96
|
+
cloudSocket.once('error', reconnect);
|
|
97
|
+
|
|
98
|
+
log(`[PROXY] → [CLOUD] Attempt ${count+1} to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
|
|
99
|
+
|
|
88
100
|
cloudSocket.connect(serverPort, server, () => {
|
|
89
101
|
log(`[PROXY] → [CLOUD] Connected to Cloud at ${server}:${serverPort}`);
|
|
102
|
+
cloudSocket.removeListener('error', reconnect);
|
|
90
103
|
|
|
91
104
|
// Send unique ID as the first message to identify this proxy/device
|
|
92
105
|
cloudSocket.write(`[${uniqueId}]`);
|
|
@@ -96,12 +109,6 @@ export function makeNewCloudConnection(master: string, masterPort: number, serve
|
|
|
96
109
|
gCloudConnections.push(context);
|
|
97
110
|
log(`[PROXY] → [CLOUD] New free connection #${gCloudConnections.length} to the Cloud at ${server}:${serverPort}`);
|
|
98
111
|
});
|
|
99
|
-
|
|
100
|
-
cloudSocket.on('error', (err) => {
|
|
101
|
-
error(`[PROXY] → [CLOUD] Failed to connect to Cloud: ${err.message}`);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
log(`[PROXY] → [CLOUD] Attempting to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
|
|
@@ -110,21 +117,32 @@ export function makeNewCloudConnection(master: string, masterPort: number, serve
|
|
|
110
117
|
////////////////
|
|
111
118
|
export function cleanStart(restart: boolean = false) {
|
|
112
119
|
if (gCloudConnections.length) {
|
|
113
|
-
gCloudConnections.forEach(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
context.deviceSocket = null;
|
|
117
|
-
}
|
|
118
|
-
context.cloudSocket.end();
|
|
119
|
-
context.cloudSocket = null;
|
|
120
|
-
});
|
|
120
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
121
|
+
log(`[PROXY] → [CLOUD] Cleaned up all ${gCloudConnections.length} cloud connections.`);
|
|
122
|
+
|
|
121
123
|
gCloudConnections.splice(0, gCloudConnections.length); // clear the array
|
|
122
|
-
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (connectionChecker) {
|
|
127
|
+
clearInterval(connectionChecker);
|
|
128
|
+
connectionChecker = null;
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
if (restart) {
|
|
126
132
|
if (config.uniqueId) {
|
|
127
133
|
makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
|
|
134
|
+
|
|
135
|
+
// check every 5 second for a free connection
|
|
136
|
+
connectionChecker = setInterval(() => {
|
|
137
|
+
const freeConnection = gCloudConnections.find((ctx: Context) =>
|
|
138
|
+
(!ctx.deviceSocket) && ctx.cloudSocket && (ctx.cloudSocket.readyState === 'open')
|
|
139
|
+
);
|
|
140
|
+
if (!freeConnection) {
|
|
141
|
+
error(`[PROXY] → [CLOUD] No free connections available, restarting proxy. ***************************`);
|
|
142
|
+
cleanStart(true);
|
|
143
|
+
}
|
|
144
|
+
}, 5000);
|
|
145
|
+
|
|
128
146
|
} else {
|
|
129
147
|
log(`[PROXY] → [CLOUD] Not restarting, no unique ID configured, not starting the proxy.`);
|
|
130
148
|
}
|
|
@@ -135,11 +153,11 @@ export function cleanStart(restart: boolean = false) {
|
|
|
135
153
|
|
|
136
154
|
|
|
137
155
|
export class Context {
|
|
138
|
-
deviceSocket: net.Socket;
|
|
156
|
+
deviceSocket: net.Socket | null;
|
|
139
157
|
master: string;
|
|
140
158
|
masterPort: number;
|
|
141
159
|
|
|
142
|
-
cloudSocket: net.Socket;
|
|
160
|
+
cloudSocket: net.Socket | null;
|
|
143
161
|
server: string;
|
|
144
162
|
serverPort: number;
|
|
145
163
|
|
|
@@ -162,8 +180,8 @@ export class Context {
|
|
|
162
180
|
|
|
163
181
|
setupCloudSocket() {
|
|
164
182
|
// set up handlers for the cloud socket
|
|
165
|
-
this.cloudSocket.on('data', data => {
|
|
166
|
-
this.handleDataFromCloud(data
|
|
183
|
+
this.cloudSocket.on('data', (data: Buffer) => {
|
|
184
|
+
this.handleDataFromCloud(data);
|
|
167
185
|
});
|
|
168
186
|
|
|
169
187
|
this.cloudSocket.on('close', () => {
|
|
@@ -210,8 +228,8 @@ export class Context {
|
|
|
210
228
|
}
|
|
211
229
|
|
|
212
230
|
} else if (this.deviceSocket.readyState === 'open') {
|
|
213
|
-
this.deviceSocket.write(
|
|
214
|
-
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().
|
|
231
|
+
this.deviceSocket.write(data);
|
|
232
|
+
debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().trim()}`);
|
|
215
233
|
|
|
216
234
|
} else {
|
|
217
235
|
warning(`[PROXY] → [DEVICE] Device socket is not open yet, waiting for connection... what to do with the data??`);
|
|
@@ -236,14 +254,14 @@ export class Context {
|
|
|
236
254
|
log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`);
|
|
237
255
|
|
|
238
256
|
this.setUpDeviceSocket();
|
|
239
|
-
|
|
240
|
-
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().
|
|
241
|
-
this.deviceSocket.write(
|
|
257
|
+
|
|
258
|
+
log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().trim()}`);
|
|
259
|
+
this.deviceSocket.write(data);
|
|
242
260
|
});
|
|
243
261
|
|
|
244
262
|
} else if (this.deviceSocket.readyState === 'open') {
|
|
245
263
|
warning(`[PROXY] → [DEVICE] Device socket already open, sending data directly -- STRANGE !!`);
|
|
246
|
-
this.deviceSocket.write(
|
|
264
|
+
this.deviceSocket.write(data);
|
|
247
265
|
|
|
248
266
|
} else {
|
|
249
267
|
error(`[PROXY] → [DEVICE] Device socket exists, but is not open yet !!`);
|
|
@@ -258,8 +276,8 @@ export class Context {
|
|
|
258
276
|
|
|
259
277
|
this.deviceSocket.on('data', (data: Buffer) => {
|
|
260
278
|
const message = data.toString('utf-8');
|
|
261
|
-
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.
|
|
262
|
-
this.cloudSocket.write(
|
|
279
|
+
debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.trim()}`);
|
|
280
|
+
this.cloudSocket.write(data);
|
|
263
281
|
});
|
|
264
282
|
|
|
265
283
|
this.deviceSocket.on('close', () => {
|
|
@@ -273,23 +291,34 @@ export class Context {
|
|
|
273
291
|
});
|
|
274
292
|
}
|
|
275
293
|
|
|
276
|
-
|
|
277
|
-
// Clean up device socket
|
|
294
|
+
cleanupSockets() {
|
|
295
|
+
// Clean up the device socket
|
|
278
296
|
if (this.deviceSocket) {
|
|
279
297
|
if (!this.deviceSocket.destroyed) {
|
|
298
|
+
this.deviceSocket.removeAllListeners();
|
|
280
299
|
this.deviceSocket.end();
|
|
300
|
+
this.deviceSocket.destroy();
|
|
281
301
|
}
|
|
282
302
|
this.deviceSocket = null;
|
|
283
303
|
}
|
|
284
304
|
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
305
|
+
// Clean up the cloud socket
|
|
306
|
+
if (this.cloudSocket) {
|
|
307
|
+
if (!this.cloudSocket.destroyed) {
|
|
308
|
+
this.cloudSocket.removeAllListeners();
|
|
309
|
+
this.cloudSocket.end();
|
|
310
|
+
this.cloudSocket.destroy();
|
|
311
|
+
}
|
|
289
312
|
this.cloudSocket = null;
|
|
290
|
-
gCloudConnections.splice(index, 1);
|
|
291
|
-
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
292
313
|
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
removeConnection() {
|
|
317
|
+
const index = gCloudConnections.indexOf(this);
|
|
318
|
+
if (index !== -1) gCloudConnections.splice(index, 1);
|
|
319
|
+
|
|
320
|
+
this.cleanupSockets();
|
|
321
|
+
log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
|
|
293
322
|
|
|
294
323
|
if (gCloudConnections.length === 0) {
|
|
295
324
|
log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
|
|
@@ -297,11 +326,11 @@ export class Context {
|
|
|
297
326
|
if (config.kind === "pm2") {
|
|
298
327
|
// PM2 should restart us.
|
|
299
328
|
log(`[PROXY] → [CLOUD] PM2 should restart us, exiting now.`);
|
|
300
|
-
process.exit();
|
|
329
|
+
setTimeout(() => process.exit(0), 100);
|
|
301
330
|
|
|
302
331
|
} else if (config.kind === "solo"){
|
|
303
332
|
log(`[PROXY] → [CLOUD] Running solo, restarting proxy.`);
|
|
304
|
-
|
|
333
|
+
restart();
|
|
305
334
|
|
|
306
335
|
} else {
|
|
307
336
|
log(`[PROXY] → [CLOUD] Running under gateway/homebridge, not restarting... not sure what to do here.`);
|
|
@@ -314,28 +343,18 @@ export class Context {
|
|
|
314
343
|
// Utilities //
|
|
315
344
|
///////////////
|
|
316
345
|
|
|
317
|
-
restart() {
|
|
318
|
-
console.log('🔄 Restarting...');
|
|
319
|
-
|
|
320
|
-
spawn(execPath, argv.slice(1), {
|
|
321
|
-
cwd: cwd(),
|
|
322
|
-
detached: true,
|
|
323
|
-
stdio: 'inherit',
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
process.exit();
|
|
327
|
-
}
|
|
328
|
-
|
|
329
346
|
// check if it's a heartbeat request from the server
|
|
330
347
|
isHeartbeatRequest(data: Buffer): boolean {
|
|
348
|
+
const msg = data.toString('utf-8').trim();
|
|
349
|
+
return msg === '[215,3]';
|
|
331
350
|
// hearbeatKind = (poll = 1, poll_old = 2, serve = 3)
|
|
332
|
-
return ((data[0] === 91) && // [
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
351
|
+
// return ((data[0] === 91) && // [
|
|
352
|
+
// (data[1] === 50) && // 2
|
|
353
|
+
// (data[2] === 49) && // 1
|
|
354
|
+
// (data[3] === 53) && // 5
|
|
355
|
+
// (data[4] === 44) && // ,
|
|
356
|
+
// (data[5] === 51) && // 3
|
|
357
|
+
// (data[6] === 93)); // ]
|
|
339
358
|
}
|
|
340
359
|
|
|
341
360
|
// check if it's a server response after connecting and sending our uniqueID
|
|
@@ -346,3 +365,64 @@ export class Context {
|
|
|
346
365
|
return (message.startsWith('[OK')) || (message.startsWith('[ERROR'));
|
|
347
366
|
}
|
|
348
367
|
}
|
|
368
|
+
|
|
369
|
+
function restart() {
|
|
370
|
+
console.log('🔄 Restarting...');
|
|
371
|
+
|
|
372
|
+
setTimeout(() => {
|
|
373
|
+
spawn(execPath, argv.slice(1), {
|
|
374
|
+
cwd: cwd(),
|
|
375
|
+
detached: true,
|
|
376
|
+
stdio: 'inherit',
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
process.exit(0);
|
|
380
|
+
}, 100);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
// Graceful shutdown handler
|
|
385
|
+
function handleShutdown(signal: string) {
|
|
386
|
+
log(`[PROXY] → [SYSTEM] Received ${signal}, shutting down gracefully...`);
|
|
387
|
+
|
|
388
|
+
if (connectionChecker)clearInterval(connectionChecker);
|
|
389
|
+
|
|
390
|
+
// Close all cloud connections
|
|
391
|
+
gCloudConnections.forEach(ctx => ctx.cleanupSockets());
|
|
392
|
+
gCloudConnections.splice(0, gCloudConnections.length);
|
|
393
|
+
|
|
394
|
+
log(`[PROXY] → [SYSTEM] All connections closed. Exiting.`);
|
|
395
|
+
|
|
396
|
+
if (config.kind === "solo") {
|
|
397
|
+
log(`[PROXY] → [SYSTEM] Restarting proxy in solo mode...`);
|
|
398
|
+
restart();
|
|
399
|
+
} else {
|
|
400
|
+
log(`[PROXY] → [SYSTEM] Exiting without restart.`);
|
|
401
|
+
setTimeout(() => process.exit(0), 100);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Listen for termination signals
|
|
406
|
+
process.on('SIGINT', () => handleShutdown('SIGINT'));
|
|
407
|
+
process.on('SIGTERM', () => handleShutdown('SIGTERM'));
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
process.on('SIGINT', (err) => {
|
|
411
|
+
error(`[SYSTEM] received SIGINT`);
|
|
412
|
+
handleShutdown('SIGINT');
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
process.on('SIGTERM', (err) => {
|
|
416
|
+
error(`[SYSTEM] received SIGTERM`);
|
|
417
|
+
handleShutdown('SIGTERM');
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
process.on('uncaughtException', (err) => {
|
|
421
|
+
error(`[SYSTEM] Uncaught Exception: ${err.message}`);
|
|
422
|
+
handleShutdown('uncaughtException');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
process.on('unhandledRejection', (reason) => {
|
|
426
|
+
error(`[SYSTEM] Unhandled Rejection: ${reason}`);
|
|
427
|
+
handleShutdown('unhandledRejection');
|
|
428
|
+
});
|