homebridge-smartsystem 6.8.3 → 6.8.4

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/config.json CHANGED
@@ -461,11 +461,11 @@
461
461
  "p1": false,
462
462
  "proxy": {
463
463
  "cloudServer": "ws.duotecno.eu",
464
- "cloudPort": 5098,
464
+ "cloudPort": 5097,
465
465
  "masterAddress": "192.168.0.97",
466
466
  "masterPort": 5001,
467
467
  "uniqueId": "",
468
468
  "debug": false,
469
- "kind": "solo"
469
+ "kind": "gw"
470
470
  }
471
471
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-smartsystem",
3
3
  "displayname": "Duotecno Bridge",
4
- "version": "6.8.3",
4
+ "version": "6.8.4",
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/proxy.js CHANGED
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Context = exports.cleanStart = exports.makeNewCloudConnection = exports.setProxyConfig = exports.kEmptyProxy = void 0;
4
4
  const net = require("net");
5
- const WebSocket = require("ws");
6
5
  const child_process_1 = require("child_process");
7
6
  const process_1 = require("process");
8
7
  exports.kEmptyProxy = {
@@ -61,17 +60,23 @@ function error(str) {
61
60
  ///////////////////////////////
62
61
  // Start up the proxy server //
63
62
  ///////////////////////////////
64
- // if (config.kind != "gw") {
65
- // makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
66
- // }
63
+ if (config.uniqueId) {
64
+ makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
65
+ }
67
66
  function makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId) {
68
- const cloudSocket = new WebSocket('ws://' + server + ':' + serverPort + "/" + uniqueId);
69
- cloudSocket.on('open', () => {
67
+ const cloudSocket = new net.Socket();
68
+ cloudSocket.connect(serverPort, server, () => {
70
69
  log(`[PROXY] → [CLOUD] Connected to Cloud at ${server}:${serverPort}`);
70
+ // Send unique ID as the first message to identify this proxy/device
71
+ cloudSocket.write(`[${uniqueId}]`);
72
+ debug(`[PROXY] → [CLOUD] Sent unique ID: ${uniqueId}`);
71
73
  const context = new Context(cloudSocket, master, masterPort, server, serverPort, uniqueId);
72
74
  gCloudConnections.push(context);
73
75
  log(`[PROXY] → [CLOUD] New free connection #${gCloudConnections.length} to the Cloud at ${server}:${serverPort}`);
74
76
  });
77
+ cloudSocket.on('error', (err) => {
78
+ error(`[PROXY] → [CLOUD] Failed to connect to Cloud: ${err.message}`);
79
+ });
75
80
  log(`[PROXY] → [CLOUD] Attempting to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
76
81
  }
77
82
  exports.makeNewCloudConnection = makeNewCloudConnection;
@@ -85,7 +90,7 @@ function cleanStart(restart = false) {
85
90
  context.deviceSocket.end();
86
91
  context.deviceSocket = null;
87
92
  }
88
- context.cloudSocket.close();
93
+ context.cloudSocket.end();
89
94
  context.cloudSocket = null;
90
95
  });
91
96
  gCloudConnections.splice(0, gCloudConnections.length); // clear the array
@@ -117,11 +122,11 @@ class Context {
117
122
  }
118
123
  setupCloudSocket() {
119
124
  // set up handlers for the cloud socket
120
- this.cloudSocket.on('message', message => {
121
- this.handleDataFromCloud(message);
125
+ this.cloudSocket.on('data', data => {
126
+ this.handleDataFromCloud(data);
122
127
  });
123
- this.cloudSocket.on('close', (code, reason) => {
124
- warning('[CLOUD] Connection closed: ' + code + ' ' + reason);
128
+ this.cloudSocket.on('close', () => {
129
+ warning('[CLOUD] Connection closed');
125
130
  if (this.deviceSocket) {
126
131
  this.deviceSocket.end();
127
132
  this.deviceSocket = null;
@@ -136,26 +141,31 @@ class Context {
136
141
  });
137
142
  }
138
143
  handleDataFromCloud(data) {
139
- debug(`[CLOUD] → [PROXY]: Data from Cloud: ${data.toString().slice(0, -1)}`);
144
+ debug(`[CLOUD] → [PROXY]: Data from Cloud: ${data.toString()}`);
140
145
  if (!this.deviceSocket) {
141
146
  // we either have a real client that wants to connect to the device,
142
147
  // or we receive a heartbeat from the cloud server
148
+ // or we receive a connection response [OK], [OK-=2], [ERROR-...]
143
149
  if (this.isHeartbeatRequest(data)) {
144
150
  debug(`[CLOUD] → [PROXY]: Received heartbeat from Cloud, returning response.`);
145
151
  // respond to the heartbeat of the cloud server
146
- this.cloudSocket.send("[72,3]"); // command([Command.HeartbeatStatus, CommandMethod.Heartbeat.server])
152
+ this.cloudSocket.write("[72,3]"); // command([Command.HeartbeatStatus, CommandMethod.Heartbeat.server])
153
+ }
154
+ else if (this.isConnectionResponse(data)) {
155
+ debug(`[CLOUD] → [PROXY]: Received connection response from Cloud: ${data.toString()}`);
156
+ // Handle connection response from the cloud server
147
157
  }
148
158
  else {
149
- // There is incomming data, it's a fresh connection, so: a new client wants to connect to the device
159
+ // There is real incomming data, it's a fresh connection, so: a new client wants to connect to the device
150
160
  // 1) create a new (free) connection to the cloud server for the next client
151
161
  // 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}.`);
162
+ log(`[PROXY] → [CLOUD] New client connection, creating new free connection for this proxy/device: ${config.uniqueId}.`);
153
163
  makeNewCloudConnection(this.master, this.masterPort, this.server, this.serverPort, this.uniqueId);
154
164
  this.makeDeviceConnection(this.master, this.masterPort, data);
155
165
  }
156
166
  }
157
167
  else if (this.deviceSocket.readyState === 'open') {
158
- this.deviceSocket.write(data);
168
+ this.deviceSocket.write(new Uint8Array(data));
159
169
  debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().slice(0, -1)}`);
160
170
  }
161
171
  else {
@@ -168,25 +178,34 @@ class Context {
168
178
  this.deviceSocket = new net.Socket();
169
179
  // Connect to local device and send the data that came in from the cloud
170
180
  this.deviceSocket.connect(port, address, () => {
181
+ // Check if socket is still valid (not cleaned up by removeConnection)
182
+ if (!this.deviceSocket) {
183
+ warning(`[PROXY] → [DEVICE] Device socket was cleaned up before connection completed`);
184
+ return;
185
+ }
171
186
  log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`);
172
187
  this.setUpDeviceSocket();
173
188
  log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().slice(0, -1)}`);
174
- this.deviceSocket.write(data);
189
+ this.deviceSocket.write(new Uint8Array(data));
175
190
  });
176
191
  }
177
192
  else if (this.deviceSocket.readyState === 'open') {
178
193
  warning(`[PROXY] → [DEVICE] Device socket already open, sending data directly -- STRANGE !!`);
179
- this.deviceSocket.write(data);
194
+ this.deviceSocket.write(new Uint8Array(data));
180
195
  }
181
196
  else {
182
197
  error(`[PROXY] → [DEVICE] Device socket exists, but is not open yet !!`);
183
198
  }
184
199
  }
185
200
  setUpDeviceSocket() {
201
+ if (!this.deviceSocket) {
202
+ error(`[PROXY] → [DEVICE] Cannot set up device socket - socket is null`);
203
+ return;
204
+ }
186
205
  this.deviceSocket.on('data', (data) => {
187
206
  const message = data.toString('utf-8');
188
207
  debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.slice(0, -1)}`);
189
- this.cloudSocket.send(message);
208
+ this.cloudSocket.write(message);
190
209
  });
191
210
  this.deviceSocket.on('close', () => {
192
211
  log(`[DEVICE] → [PROXY] Device socket closed`);
@@ -198,11 +217,17 @@ class Context {
198
217
  });
199
218
  }
200
219
  removeConnection() {
201
- this.deviceSocket = null;
220
+ // Clean up device socket
221
+ if (this.deviceSocket) {
222
+ if (!this.deviceSocket.destroyed) {
223
+ this.deviceSocket.end();
224
+ }
225
+ this.deviceSocket = null;
226
+ }
202
227
  // remove this context and close the cloud socket
203
228
  const index = gCloudConnections.indexOf(this);
204
229
  if (index !== -1) {
205
- this.cloudSocket.close();
230
+ this.cloudSocket.end();
206
231
  this.cloudSocket = null;
207
232
  gCloudConnections.splice(index, 1);
208
233
  log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
@@ -238,19 +263,20 @@ class Context {
238
263
  }
239
264
  // check if it's a heartbeat request from the server
240
265
  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
- }
266
+ // hearbeatKind = (poll = 1, poll_old = 2, serve = 3)
267
+ return ((data[0] === 91) && // [
268
+ (data[1] === 50) && // 2
269
+ (data[2] === 49) && // 1
270
+ (data[3] === 53) && // 5
271
+ (data[4] === 44) && // ,
272
+ (data[5] === 51) && // 3
273
+ (data[6] === 93)); // ]
274
+ }
275
+ // check if it's a server response after connecting and sending our uniqueID
276
+ isConnectionResponse(data) {
277
+ const message = data.toString();
278
+ // Check for various server response patterns
279
+ return (message.startsWith('[OK')) || (message.startsWith('[ERROR'));
254
280
  }
255
281
  }
256
282
  exports.Context = Context;
package/server/proxy.ts CHANGED
@@ -1,10 +1,9 @@
1
1
  import * as net from 'net';
2
- import * as WebSocket from 'ws';
3
2
  import { spawn } from 'child_process';
4
3
  import { argv, cwd, execPath } from 'process';
5
4
 
6
5
  //
7
- // [ device (TCP) ] ←→ [ Proxy = WebSocket client (ws) ] ←→ [ Cloud = 2 x WebSocket server (ws) ] ←→ [ browser client ]
6
+ // [ device (TCP) ] ←→ [ Proxy = TCP client ] ←→ [ Cloud = TCP server ] ←→ [ browser client ]
8
7
  //
9
8
 
10
9
  export interface ProxyConfig {
@@ -16,6 +15,7 @@ export interface ProxyConfig {
16
15
  debug?: boolean; // debug mode
17
16
  kind?: "pm2" | "gw" | "solo";
18
17
  }
18
+
19
19
  export const kEmptyProxy: ProxyConfig = {
20
20
  cloudServer: "ws.duotecno.eu",
21
21
  cloudPort: 5098,
@@ -50,6 +50,8 @@ try {
50
50
  console.log("No config-proxy.json found, using default values from homebridge or other.");
51
51
  }
52
52
 
53
+
54
+
53
55
  const gCloudConnections: Array<Context> = [];
54
56
  const kDebug = config.debug || false; // enable debug mode from config
55
57
 
@@ -75,26 +77,34 @@ function error(str: string) {
75
77
  ///////////////////////////////
76
78
  // Start up the proxy server //
77
79
  ///////////////////////////////
78
-
79
- // if (config.kind != "gw") {
80
- // makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
81
- // }
80
+ if (config.uniqueId) {
81
+ makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
82
+ }
82
83
 
83
84
 
84
85
  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', () => {
86
+ const cloudSocket = new net.Socket();
87
+
88
+ cloudSocket.connect(serverPort, server, () => {
88
89
  log(`[PROXY] → [CLOUD] Connected to Cloud at ${server}:${serverPort}`);
90
+
91
+ // Send unique ID as the first message to identify this proxy/device
92
+ cloudSocket.write(`[${uniqueId}]`);
93
+ debug(`[PROXY] → [CLOUD] Sent unique ID: ${uniqueId}`);
89
94
 
90
95
  const context = new Context(cloudSocket, master, masterPort, server, serverPort, uniqueId);
91
96
  gCloudConnections.push(context);
92
97
  log(`[PROXY] → [CLOUD] New free connection #${gCloudConnections.length} to the Cloud at ${server}:${serverPort}`);
93
98
  });
94
99
 
100
+ cloudSocket.on('error', (err) => {
101
+ error(`[PROXY] → [CLOUD] Failed to connect to Cloud: ${err.message}`);
102
+ });
103
+
95
104
  log(`[PROXY] → [CLOUD] Attempting to start a new free connection for ${config.uniqueId} to the Cloud at ${server}:${serverPort}`);
96
105
  }
97
106
 
107
+
98
108
  ////////////////
99
109
  // Restarting //
100
110
  ////////////////
@@ -105,7 +115,7 @@ export function cleanStart(restart: boolean = false) {
105
115
  context.deviceSocket.end();
106
116
  context.deviceSocket = null;
107
117
  }
108
- context.cloudSocket.close();
118
+ context.cloudSocket.end();
109
119
  context.cloudSocket = null;
110
120
  });
111
121
  gCloudConnections.splice(0, gCloudConnections.length); // clear the array
@@ -129,14 +139,14 @@ export class Context {
129
139
  master: string;
130
140
  masterPort: number;
131
141
 
132
- cloudSocket: WebSocket;
142
+ cloudSocket: net.Socket;
133
143
  server: string;
134
144
  serverPort: number;
135
145
 
136
146
  // unique identifier for this device-gateway combination
137
147
  uniqueId: string;
138
148
 
139
- constructor(cloudSocket: WebSocket, master: string, masterPort: number, server: string, serverPort: number, uniqueId: string) {
149
+ constructor(cloudSocket: net.Socket, master: string, masterPort: number, server: string, serverPort: number, uniqueId: string) {
140
150
  this.deviceSocket = null;
141
151
  this.master = master;
142
152
  this.masterPort = masterPort;
@@ -152,12 +162,12 @@ export class Context {
152
162
 
153
163
  setupCloudSocket() {
154
164
  // set up handlers for the cloud socket
155
- this.cloudSocket.on('message', message => {
156
- this.handleDataFromCloud(message as Buffer);
165
+ this.cloudSocket.on('data', data => {
166
+ this.handleDataFromCloud(data as Buffer);
157
167
  });
158
168
 
159
- this.cloudSocket.on('close', (code, reason) => {
160
- warning('[CLOUD] Connection closed: ' + code + ' ' + reason);
169
+ this.cloudSocket.on('close', () => {
170
+ warning('[CLOUD] Connection closed');
161
171
  if (this.deviceSocket) {
162
172
  this.deviceSocket.end();
163
173
  this.deviceSocket = null;
@@ -174,28 +184,33 @@ export class Context {
174
184
  }
175
185
 
176
186
  handleDataFromCloud(data: Buffer) {
177
- debug(`[CLOUD] → [PROXY]: Data from Cloud: ${data.toString().slice(0, -1)}`);
187
+ debug(`[CLOUD] → [PROXY]: Data from Cloud: ${data.toString()}`);
178
188
 
179
189
  if (!this.deviceSocket) {
180
190
  // we either have a real client that wants to connect to the device,
181
191
  // or we receive a heartbeat from the cloud server
192
+ // or we receive a connection response [OK], [OK-=2], [ERROR-...]
182
193
 
183
194
  if (this.isHeartbeatRequest(data)) {
184
195
  debug(`[CLOUD] → [PROXY]: Received heartbeat from Cloud, returning response.`);
185
196
  // respond to the heartbeat of the cloud server
186
- this.cloudSocket.send("[72,3]"); // command([Command.HeartbeatStatus, CommandMethod.Heartbeat.server])
197
+ this.cloudSocket.write("[72,3]"); // command([Command.HeartbeatStatus, CommandMethod.Heartbeat.server])
187
198
 
199
+ } else if (this.isConnectionResponse(data)) {
200
+ debug(`[CLOUD] → [PROXY]: Received connection response from Cloud: ${data.toString()}`);
201
+ // Handle connection response from the cloud server
202
+
188
203
  } else {
189
- // There is incomming data, it's a fresh connection, so: a new client wants to connect to the device
204
+ // There is real incomming data, it's a fresh connection, so: a new client wants to connect to the device
190
205
  // 1) create a new (free) connection to the cloud server for the next client
191
206
  // 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}.`);
207
+ log(`[PROXY] → [CLOUD] New client connection, creating new free connection for this proxy/device: ${config.uniqueId}.`);
193
208
  makeNewCloudConnection(this.master, this.masterPort, this.server, this.serverPort, this.uniqueId);
194
209
  this.makeDeviceConnection(this.master, this.masterPort, data);
195
210
  }
196
211
 
197
212
  } else if (this.deviceSocket.readyState === 'open') {
198
- this.deviceSocket.write(data);
213
+ this.deviceSocket.write(new Uint8Array(data));
199
214
  debug(`[PROXY] → [DEVICE] forwarding data to device: ${data.toString().slice(0, -1)}`);
200
215
 
201
216
  } else {
@@ -212,17 +227,23 @@ export class Context {
212
227
 
213
228
  // Connect to local device and send the data that came in from the cloud
214
229
  this.deviceSocket.connect(port, address, () => {
230
+ // Check if socket is still valid (not cleaned up by removeConnection)
231
+ if (!this.deviceSocket) {
232
+ warning(`[PROXY] → [DEVICE] Device socket was cleaned up before connection completed`);
233
+ return;
234
+ }
235
+
215
236
  log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`);
216
237
 
217
238
  this.setUpDeviceSocket();
218
239
 
219
240
  log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().slice(0, -1)}`);
220
- this.deviceSocket.write(data);
241
+ this.deviceSocket.write(new Uint8Array(data));
221
242
  });
222
243
 
223
244
  } else if (this.deviceSocket.readyState === 'open') {
224
245
  warning(`[PROXY] → [DEVICE] Device socket already open, sending data directly -- STRANGE !!`);
225
- this.deviceSocket.write(data);
246
+ this.deviceSocket.write(new Uint8Array(data));
226
247
 
227
248
  } else {
228
249
  error(`[PROXY] → [DEVICE] Device socket exists, but is not open yet !!`);
@@ -230,10 +251,15 @@ export class Context {
230
251
  }
231
252
 
232
253
  setUpDeviceSocket() {
254
+ if (!this.deviceSocket) {
255
+ error(`[PROXY] → [DEVICE] Cannot set up device socket - socket is null`);
256
+ return;
257
+ }
258
+
233
259
  this.deviceSocket.on('data', (data: Buffer) => {
234
260
  const message = data.toString('utf-8');
235
261
  debug(`[DEVICE] → [PROXY] forwarding data to Cloud: ${message.slice(0, -1)}`);
236
- this.cloudSocket.send(message);
262
+ this.cloudSocket.write(message);
237
263
  });
238
264
 
239
265
  this.deviceSocket.on('close', () => {
@@ -248,12 +274,18 @@ export class Context {
248
274
  }
249
275
 
250
276
  removeConnection() {
251
- this.deviceSocket = null;
277
+ // Clean up device socket
278
+ if (this.deviceSocket) {
279
+ if (!this.deviceSocket.destroyed) {
280
+ this.deviceSocket.end();
281
+ }
282
+ this.deviceSocket = null;
283
+ }
252
284
 
253
285
  // remove this context and close the cloud socket
254
286
  const index = gCloudConnections.indexOf(this);
255
287
  if (index !== -1) {
256
- this.cloudSocket.close();
288
+ this.cloudSocket.end();
257
289
  this.cloudSocket = null;
258
290
  gCloudConnections.splice(index, 1);
259
291
  log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
@@ -262,7 +294,6 @@ export class Context {
262
294
  if (gCloudConnections.length === 0) {
263
295
  log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
264
296
  // just to be sure: restart...
265
-
266
297
  if (config.kind === "pm2") {
267
298
  // PM2 should restart us.
268
299
  log(`[PROXY] → [CLOUD] PM2 should restart us, exiting now.`);
@@ -297,19 +328,21 @@ export class Context {
297
328
 
298
329
  // check if it's a heartbeat request from the server
299
330
  isHeartbeatRequest(data: Buffer): boolean {
300
- // heartbeatKind = (poll = 1, poll_old = 2, serve = 3)
301
-
302
- if (typeof data === 'string') {
303
- return data === "[215,3]";
331
+ // hearbeatKind = (poll = 1, poll_old = 2, serve = 3)
332
+ return ((data[0] === 91) && // [
333
+ (data[1] === 50) && // 2
334
+ (data[2] === 49) && // 1
335
+ (data[3] === 53) && // 5
336
+ (data[4] === 44) && // ,
337
+ (data[5] === 51) && // 3
338
+ (data[6] === 93)); // ]
339
+ }
304
340
 
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
- }
341
+ // check if it's a server response after connecting and sending our uniqueID
342
+ isConnectionResponse(data: Buffer): boolean {
343
+ const message = data.toString();
344
+
345
+ // Check for various server response patterns
346
+ return (message.startsWith('[OK')) || (message.startsWith('[ERROR'));
314
347
  }
315
348
  }