homebridge-smartsystem 7.0.3 → 7.0.6

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 (64) hide show
  1. package/README.md +57 -1
  2. package/duotecno/types.js +2 -1
  3. package/package.json +1 -1
  4. package/server/mDNS.js +44 -15
  5. package/server/platform.js +7 -0
  6. package/server/powerbase.js +11 -8
  7. package/server/proxy.js +69 -25
  8. package/server/shelly.js +1 -1
  9. package/server/smartapp.js +1 -31
  10. package/server/views/proxy.ejs +73 -1
  11. package/server/webapp.js +9 -1
  12. package/.gitattributes +0 -4
  13. package/MIXINS-GUIDE.md +0 -210
  14. package/SETTINGS-UPDATE.md +0 -195
  15. package/TESTING-GUIDE.md +0 -220
  16. package/WEBSOCKET-API.md +0 -303
  17. package/accessories/accessory.ts +0 -128
  18. package/accessories/bulb.ts +0 -38
  19. package/accessories/dimmer.ts +0 -97
  20. package/accessories/door.ts +0 -88
  21. package/accessories/garagedoor.ts +0 -134
  22. package/accessories/lock.ts +0 -103
  23. package/accessories/mood.ts +0 -68
  24. package/accessories/switch.ts +0 -34
  25. package/accessories/temperature.ts +0 -55
  26. package/accessories/windowcovering.ts +0 -189
  27. package/config.homebridge.json +0 -23
  28. package/config.json +0 -509
  29. package/duotecno/Q.ts +0 -56
  30. package/duotecno/api.ts +0 -63
  31. package/duotecno/colors.ts +0 -96
  32. package/duotecno/config.ts +0 -125
  33. package/duotecno/logger.ts +0 -43
  34. package/duotecno/master.ts +0 -884
  35. package/duotecno/protocol.ts +0 -992
  36. package/duotecno/smartsocket.ts +0 -118
  37. package/duotecno/system.ts +0 -439
  38. package/duotecno/types.ts +0 -899
  39. package/index.ts +0 -9
  40. package/log.txt +0 -74
  41. package/raspberry-pi-setup.sh +0 -80
  42. package/server/HA-API.ts +0 -102
  43. package/server/HB.ts +0 -343
  44. package/server/base.ts +0 -54
  45. package/server/mDNS.ts +0 -198
  46. package/server/p1.ts +0 -226
  47. package/server/platform.ts +0 -305
  48. package/server/powerbase.ts +0 -315
  49. package/server/proxy.ts +0 -466
  50. package/server/shelly.ts +0 -212
  51. package/server/smappee.ts +0 -309
  52. package/server/smartapp.ts +0 -1710
  53. package/server/socapp.ts +0 -182
  54. package/server/somfy.ts +0 -174
  55. package/server/support.ts +0 -142
  56. package/server/webapp.ts +0 -678
  57. package/testHB.js +0 -41
  58. package/testHB.ts +0 -54
  59. package/testProxy.js +0 -7
  60. package/testProxy.ts +0 -6
  61. package/testWS.js +0 -137
  62. package/testWS.ts +0 -174
  63. package/update.sh +0 -16
  64. package/updserv.sh +0 -13
package/README.md CHANGED
@@ -319,6 +319,9 @@ Homebridge UI version
319
319
 
320
320
  ### v7.0.0 - 12/11/2025
321
321
  - 0: added mDNS/Bonjour + always port 80 + don't preload EJS files if system.debug == true
322
+ - 4: fixed a bug when shelly has empty list of ip addresses
323
+ - 5: better error handling in the proxy.ts + show connection in the /proxy page
324
+
322
325
 
323
326
  ## Hardware
324
327
  A Raspberry Pi that connects to a Duotecno IP Node
@@ -630,4 +633,57 @@ Accessory.Categories = {
630
633
  SHOWER_HEAD: 30,
631
634
  TELEVISION: 31,
632
635
  TARGET_CONTROLLER: 32
633
- };
636
+ };
637
+
638
+
639
+ Small example of config.
640
+
641
+ {
642
+ "manufacturer": "Duotecno-Coppieters",
643
+ "platform": "DuotecnoPlatform",
644
+ "debug": false,
645
+ "smappee": {
646
+ "rules": [],
647
+ "address": "",
648
+ "uid": "57e3e0d8-bb05-4b04-8662-1a9871998f3f",
649
+ "debug": false,
650
+ "bindings": []
651
+ },
652
+ "smartapp": {
653
+ "port": 5002,
654
+ "switches": [],
655
+ "debug": true,
656
+ "links": [],
657
+ "apiHost": "localhost",
658
+ "apiPort": 8581,
659
+ "username": "duotecno",
660
+ "password": "#Duotecno@1",
661
+ "devices": []
662
+ },
663
+ "system": {
664
+ "language": "EN",
665
+ "stores": false,
666
+ "multiple": false,
667
+ "socketserver": "gateway.duotecno.com",
668
+ "socketport": 9999,
669
+ "mood": "sfeer",
670
+ "cmasters": [],
671
+ "cunits": [],
672
+ "debug": false
673
+ },
674
+ "socapp": false,
675
+ "p1": {
676
+ "debug": false,
677
+ "rules": [],
678
+ "bindings": [],
679
+ "address": ""
680
+ },
681
+ "shelly": {
682
+ "debug": false,
683
+ "address": "",
684
+ "addresses": "",
685
+ "rules": [],
686
+ "bindings": []
687
+ },
688
+ "proxy": false
689
+ }
package/duotecno/types.js CHANGED
@@ -407,12 +407,13 @@ exports.Sanitizers = {
407
407
  ///////////
408
408
  proxy: function (config, system) {
409
409
  var _a, _b;
410
- const c = proxy_1.kEmptyProxy;
410
+ const c = Object.assign({}, proxy_1.kEmptyProxy);
411
411
  c.cloudServer = (config === null || config === void 0 ? void 0 : config.cloudServer) || proxy_1.kEmptyProxy.cloudServer;
412
412
  c.cloudPort = (config === null || config === void 0 ? void 0 : config.cloudPort) || proxy_1.kEmptyProxy.cloudPort;
413
413
  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) || "";
414
414
  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;
415
415
  c.uniqueId = (config === null || config === void 0 ? void 0 : config.uniqueId) || proxy_1.kEmptyProxy.uniqueId;
416
+ c.kind = "gw";
416
417
  return c;
417
418
  },
418
419
  //////////////
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-smartsystem",
3
3
  "displayname": "Duotecno Bridge",
4
- "version": "7.0.3",
4
+ "version": "7.0.6",
5
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",
package/server/mDNS.js CHANGED
@@ -40,26 +40,55 @@ class MDNSService {
40
40
  const ports = Array.isArray(port) ? port : [port];
41
41
  const primaryPort = ports[0];
42
42
  // Register the HTTP service using bonjour
43
- const service = this.bonjour.publish({
44
- name,
45
- type: 'http',
46
- port: primaryPort,
47
- txt: {
48
- version: version || '1.0.0',
49
- ports: ports.join(','),
50
- path: '/'
43
+ // Wrap in try-catch because bonjour can throw synchronously on conflicts
44
+ let service;
45
+ try {
46
+ service = this.bonjour.publish({
47
+ name,
48
+ type: 'http',
49
+ port: primaryPort,
50
+ txt: {
51
+ version: version || '1.0.0',
52
+ ports: ports.join(','),
53
+ path: '/'
54
+ }
55
+ });
56
+ (0, logger_1.log)("mDNS", `HTTP service registered: ${name}.local on port(s) ${ports.join(', ')}`);
57
+ }
58
+ catch (publishError) {
59
+ if (publishError.message && publishError.message.includes('already in use')) {
60
+ (0, logger_1.err)("mDNS", `Service name "${name}" is already in use. This is usually harmless - continuing anyway.`);
61
+ (0, logger_1.err)("mDNS", "The old service registration will expire in ~30 seconds.");
51
62
  }
52
- });
53
- (0, logger_1.log)("mDNS", `HTTP service registered: ${name}.local on port(s) ${ports.join(', ')}`);
54
- // Handle service errors
55
- service.on('error', (error) => {
56
- (0, logger_1.err)("mDNS", `Service error: ${error.message}`);
57
- });
63
+ else {
64
+ (0, logger_1.err)("mDNS", `Service publish error: ${publishError.message}`);
65
+ }
66
+ // Continue anyway - service will work once the old registration expires
67
+ }
68
+ // Handle service errors - especially "already in use" errors
69
+ if (service) {
70
+ service.on('error', (error) => {
71
+ if (error.message.includes('already in use')) {
72
+ (0, logger_1.err)("mDNS", `Service name "${name}" is already in use. Another instance may be running.`);
73
+ (0, logger_1.err)("mDNS", "To fix: 1) Stop other instances, or 2) Change the mdnsName in settings, or 3) Wait 30s for old service to expire");
74
+ }
75
+ else {
76
+ (0, logger_1.err)("mDNS", `Service error: ${error.message}`);
77
+ }
78
+ });
79
+ }
58
80
  // Publish the hostname A record (like ESP32 MDNS.begin())
59
81
  this.publishHostname(name);
60
82
  }
61
83
  catch (e) {
62
- (0, logger_1.err)("mDNS", `Failed to register service: ${e.message}`);
84
+ if (e.message.includes('already in use')) {
85
+ (0, logger_1.err)("mDNS", `Service name "${this.config.name}" is already in use on the network`);
86
+ (0, logger_1.err)("mDNS", "Possible causes: 1) Another instance is running, 2) Previous instance didn't clean up properly");
87
+ (0, logger_1.err)("mDNS", "Solutions: Stop other instances, or restart with a different service name in settings");
88
+ }
89
+ else {
90
+ (0, logger_1.err)("mDNS", `Failed to register service: ${e.message.split('\n')[0]}`);
91
+ }
63
92
  }
64
93
  }
65
94
  /**
@@ -48,6 +48,13 @@ class Platform extends base_1.Base {
48
48
  (0, logger_1.log)("homebridge", "try doing upgrade from pre v6.2 versions");
49
49
  this.doUpgradeV62();
50
50
  }
51
+ // we're running under HomeBridge/Platform -> always set proxy kind to "gw"
52
+ if (!this.config.proxy) {
53
+ this.config.proxy = Object.assign({}, proxy_1.kEmptyProxy);
54
+ }
55
+ else if (this.config.proxy.kind !== "gw") {
56
+ this.config.proxy.kind = "gw";
57
+ }
51
58
  (0, logger_1.log)("homebridge", "running in " + ((this.config.debug) ? "debug" : "prod") + " mode in directory: " + process.cwd());
52
59
  (0, logger_1.log)("homebridge", "with config:" + JSON.stringify(this.config, null, 2));
53
60
  // save config for other components
@@ -79,16 +79,17 @@ class PowerBase extends base_1.Base {
79
79
  });
80
80
  }
81
81
  evalPower(exp) {
82
- var _a, _b, _c, _d, _e, _f;
82
+ var _a, _b, _c, _d;
83
83
  let parsed = exp.split(/(\+|-)/);
84
84
  let result = (_b = (_a = this.channels[parseInt(parsed[0]) || 0]) === null || _a === void 0 ? void 0 : _a.power) !== null && _b !== void 0 ? _b : 0;
85
85
  for (let i = 1; i < parsed.length; i = i + 2) {
86
+ const channelIndex = parseInt(parsed[i + 1]) || 0;
87
+ const channelPower = (_d = (_c = this.channels[channelIndex]) === null || _c === void 0 ? void 0 : _c.power) !== null && _d !== void 0 ? _d : 0;
86
88
  if (parsed[i] === "-")
87
- result -= (_d = (_c = this.channels[parseInt(parsed[i + 1]) || 0]) === null || _c === void 0 ? void 0 : _c.power) !== null && _d !== void 0 ? _d : 0;
89
+ result -= channelPower;
88
90
  else if (parsed[i] === "+")
89
- result += (_f = (_e = this.channels[parseInt(parsed[i + 1]) || 0]) === null || _e === void 0 ? void 0 : _e.power) !== null && _f !== void 0 ? _f : 0;
91
+ result += channelPower;
90
92
  }
91
- (0, logger_1.debug)("power", "evalPower: " + exp + " -> " + parsed + " = " + result);
92
93
  return result;
93
94
  }
94
95
  //////////////////////////////////////////////
@@ -114,6 +115,7 @@ class PowerBase extends base_1.Base {
114
115
  // for all bindings
115
116
  this.bindings.forEach(b => {
116
117
  const power = this.evalPower(b.channel);
118
+ (0, logger_1.debug)("power", `Binding channel=${b.channel}, register=${b.register}, current_value=${b.value}, calculated_power=${power}`);
117
119
  if (this.significant(power, b.value)) {
118
120
  (0, logger_1.debug)("power", "APPLY to " + b.channel + " = " + b.value + " -> " + power + " for " + b.register +
119
121
  "): difference: " + Math.abs(b.value - power) +
@@ -124,10 +126,11 @@ class PowerBase extends base_1.Base {
124
126
  (0, logger_1.debug)("power", "set register " + b.register + " to " + power);
125
127
  })
126
128
  .catch((e) => (0, logger_1.err)(this.type, e.message || e));
127
- // } else {
128
- // debug("power", "DONT to " + b.channel + " = " + b.power + " -> " + power + " for " + b.register +
129
- // " : difference: " + Math.abs(b.power - power) +
130
- // " > threshold: 5% =" + Math.abs(b.power * 0.05) + " > threshold: 2% =" + Math.abs(b.power * 0.02));
129
+ }
130
+ else {
131
+ (0, logger_1.debug)("power", "NOT significant for " + b.channel + " = " + b.value + " -> " + power + " for register " + b.register +
132
+ ": difference: " + Math.abs(b.value - power) +
133
+ " <= threshold: 5% =" + Math.abs(b.value * 0.05) + " or 2% =" + Math.abs(b.value * 0.02));
131
134
  }
132
135
  });
133
136
  }
package/server/proxy.js CHANGED
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.Context = exports.cleanStart = exports.makeNewCloudConnection = exports.setProxyConfig = exports.setWebApp = exports.kEmptyProxy = void 0;
12
+ exports.Context = exports.cleanStart = exports.makeNewCloudConnection = exports.gCloudConnections = exports.setProxyConfig = exports.setWebApp = exports.kEmptyProxy = void 0;
13
13
  const net = require("net");
14
14
  const child_process_1 = require("child_process");
15
15
  const process_1 = require("process");
@@ -20,7 +20,7 @@ exports.kEmptyProxy = {
20
20
  masterPort: 5001,
21
21
  uniqueId: "",
22
22
  debug: false,
23
- kind: "solo" // default running under PM2 or other process manager -> don't restart yourself
23
+ kind: "gw" // default running under the gateway or other process manager -> don't restart yourself
24
24
  };
25
25
  const config = Object.assign({}, exports.kEmptyProxy);
26
26
  // Store reference to the webapp so we can close servers before restarting
@@ -53,7 +53,7 @@ catch (e) {
53
53
  log("[PROXY] No config-proxy.json found, using default values from homebridge or other.");
54
54
  }
55
55
  let connectionChecker = null;
56
- const gCloudConnections = [];
56
+ exports.gCloudConnections = [];
57
57
  const kDebug = config.debug || false; // enable debug mode from config
58
58
  /////////////
59
59
  // Logging //
@@ -108,8 +108,8 @@ function makeNewCloudConnection(master, masterPort, server, serverPort, uniqueId
108
108
  cloudSocket.write(`[${uniqueId}]`);
109
109
  debug(`[PROXY] → [CLOUD] Sent unique ID: ${uniqueId}`);
110
110
  const context = new Context(cloudSocket, master, masterPort, server, serverPort, uniqueId);
111
- gCloudConnections.push(context);
112
- log(`[PROXY] → [CLOUD] New free connection #${gCloudConnections.length} to the Cloud at ${server}:${serverPort}`);
111
+ exports.gCloudConnections.push(context);
112
+ log(`[PROXY] → [CLOUD] New free connection #${exports.gCloudConnections.length} to the Cloud at ${server}:${serverPort}`);
113
113
  });
114
114
  }
115
115
  exports.makeNewCloudConnection = makeNewCloudConnection;
@@ -117,22 +117,23 @@ exports.makeNewCloudConnection = makeNewCloudConnection;
117
117
  // Restarting //
118
118
  ////////////////
119
119
  function cleanStart(restart = false) {
120
- if (gCloudConnections.length) {
121
- gCloudConnections.forEach(ctx => ctx.cleanupSockets());
122
- log(`[PROXY] → [CLOUD] Cleaned up all ${gCloudConnections.length} cloud connections.`);
123
- gCloudConnections.splice(0, gCloudConnections.length); // clear the array
120
+ if (exports.gCloudConnections === null || exports.gCloudConnections === void 0 ? void 0 : exports.gCloudConnections.length) {
121
+ exports.gCloudConnections.forEach(ctx => ctx.cleanupSockets());
122
+ log(`[PROXY] → [CLOUD] Cleaned up all ${exports.gCloudConnections.length} cloud connections.`);
123
+ exports.gCloudConnections.splice(0, exports.gCloudConnections.length); // clear the array
124
124
  }
125
125
  if (connectionChecker) {
126
126
  clearInterval(connectionChecker);
127
127
  connectionChecker = null;
128
128
  }
129
129
  if (restart) {
130
+ log(`[PROXY] → [CLOUD] Restarting proxy... for ${config.uniqueId}.`);
130
131
  if (config.uniqueId) {
131
132
  log(`[PROXY] Starting proxy with config: ${JSON.stringify(config, null, 2)}`);
132
133
  makeNewCloudConnection(config.masterAddress, config.masterPort, config.cloudServer, config.cloudPort, config.uniqueId);
133
134
  // check every 16 second for a free connection
134
135
  connectionChecker = setInterval(() => {
135
- const freeConnection = gCloudConnections.find((ctx) => (!ctx.deviceSocket) && ctx.cloudSocket && (ctx.cloudSocket.readyState === 'open'));
136
+ const freeConnection = exports.gCloudConnections.find((ctx) => (!ctx.deviceSocket) && ctx.cloudSocket && (ctx.cloudSocket.readyState === 'open'));
136
137
  if (freeConnection) {
137
138
  debug(`[PROXY] → [CLOUD] Found free connection #${freeConnection.uniqueId} to the Cloud -> we're still ok.`);
138
139
  }
@@ -160,6 +161,13 @@ class Context {
160
161
  this.server = server;
161
162
  this.serverPort = serverPort;
162
163
  this.uniqueId = uniqueId;
164
+ this.deviceStatus = {
165
+ connected: false,
166
+ lastAttempt: null,
167
+ lastSuccess: null,
168
+ lastError: null,
169
+ retryCount: 0
170
+ };
163
171
  this.setupCloudSocket();
164
172
  }
165
173
  setupCloudSocket() {
@@ -216,8 +224,25 @@ class Context {
216
224
  }
217
225
  makeDeviceConnection(address, port, data) {
218
226
  log(`[PROXY] → [DEVICE] Attempting to connect to local device at ${address}:${port}`);
227
+ this.deviceStatus.lastAttempt = new Date();
219
228
  if (!this.deviceSocket) {
220
229
  this.deviceSocket = new net.Socket();
230
+ // Add error handler BEFORE connecting to prevent uncaught exceptions
231
+ this.deviceSocket.on('error', (err) => {
232
+ error(`[DEVICE] → [PROXY] Connection error: ${err.message}`);
233
+ // Update device status
234
+ this.deviceStatus.connected = false;
235
+ this.deviceStatus.lastError = err.message;
236
+ this.deviceStatus.retryCount++;
237
+ log(`[DEVICE] → [PROXY] Connection failed. Will retry on next cloud request.`);
238
+ // Clean up this socket
239
+ if (this.deviceSocket && !this.deviceSocket.destroyed) {
240
+ this.deviceSocket.destroy();
241
+ }
242
+ this.deviceSocket = null;
243
+ // Don't create a new cloud connection - we already have one!
244
+ // The device connection will be retried when the next data comes from the cloud
245
+ });
221
246
  // Connect to local device and send the data that came in from the cloud
222
247
  this.deviceSocket.connect(port, address, () => {
223
248
  // Check if socket is still valid (not cleaned up by removeConnection)
@@ -226,6 +251,11 @@ class Context {
226
251
  return;
227
252
  }
228
253
  log(`[PROXY] → [DEVICE] Connected to local device at ${address}:${port}`);
254
+ // Update device status
255
+ this.deviceStatus.connected = true;
256
+ this.deviceStatus.lastSuccess = new Date();
257
+ this.deviceStatus.lastError = null;
258
+ this.deviceStatus.retryCount = 0;
229
259
  this.setUpDeviceSocket();
230
260
  log(`[PROXY] → [DEVICE] Sending initial message: ${data.toString().trim()}`);
231
261
  this.deviceSocket.write(data);
@@ -253,10 +283,8 @@ class Context {
253
283
  log(`[DEVICE] → [PROXY] Device socket closed`);
254
284
  this.removeConnection();
255
285
  });
256
- this.deviceSocket.on('error', (err) => {
257
- error(`[DEVICE] → [PROXY] Device socket error: ${err.message}`);
258
- this.removeConnection();
259
- });
286
+ // Note: error handler is already added in makeDeviceConnection()
287
+ // Don't add duplicate error handler here
260
288
  }
261
289
  cleanupSockets() {
262
290
  // Clean up the device socket
@@ -279,12 +307,12 @@ class Context {
279
307
  }
280
308
  }
281
309
  removeConnection() {
282
- const index = gCloudConnections.indexOf(this);
310
+ const index = exports.gCloudConnections.indexOf(this);
283
311
  if (index !== -1)
284
- gCloudConnections.splice(index, 1);
312
+ exports.gCloudConnections.splice(index, 1);
285
313
  this.cleanupSockets();
286
314
  log(`[PROXY] → [CLOUD] Closed socket to Cloud and removed context for device ${this.master}`);
287
- if (gCloudConnections.length === 0) {
315
+ if (exports.gCloudConnections && exports.gCloudConnections.length === 0) {
288
316
  log(`[PROXY] → [CLOUD] No more cloud connections, restarting proxy.`);
289
317
  // just to be sure: restart...
290
318
  if (config.kind === "pm2") {
@@ -345,8 +373,10 @@ function handleShutdown(signal) {
345
373
  connectionChecker = null;
346
374
  }
347
375
  // Close all cloud connections
348
- gCloudConnections.forEach(ctx => ctx.cleanupSockets());
349
- gCloudConnections.splice(0, gCloudConnections.length);
376
+ if (exports.gCloudConnections && exports.gCloudConnections.length > 0) {
377
+ exports.gCloudConnections.forEach(ctx => ctx.cleanupSockets());
378
+ exports.gCloudConnections.splice(0, exports.gCloudConnections.length);
379
+ }
350
380
  // Close HTTP servers if available
351
381
  if (gWebApp && typeof gWebApp.closeServers === 'function') {
352
382
  log(`[PROXY] → [SYSTEM] Closing HTTP servers...`);
@@ -357,15 +387,16 @@ function handleShutdown(signal) {
357
387
  error(`[PROXY] → [SYSTEM] Error closing servers: ${e.message}`);
358
388
  }
359
389
  }
360
- log(`[PROXY] → [SYSTEM] All connections closed. Exiting.`);
390
+ log(`[PROXY] → [SYSTEM] All connections closed.`);
361
391
  if (config.kind === "solo") {
362
392
  log(`[PROXY] → [SYSTEM] Restarting proxy in solo mode...`);
363
393
  restart();
364
394
  }
365
395
  else {
366
- log(`[PROXY] → [SYSTEM] Exiting without restart.`);
367
- // Give OS time to release ports before exit (helps prevent EADDRINUSE on restart)
368
- setTimeout(() => process.exit(0), 500);
396
+ log(`[PROXY] → [SYSTEM] Running under HomeBridge/Gateway - NOT calling process.exit() to avoid restarting entire system.`);
397
+ log(`[PROXY] → [SYSTEM] Plugin will remain running. Check /settings page for connection status.`);
398
+ // Don't call process.exit() when running under HomeBridge!
399
+ // This would restart the entire HomeBridge + UI system
369
400
  }
370
401
  });
371
402
  }
@@ -381,11 +412,24 @@ process.on('SIGTERM', (err) => {
381
412
  handleShutdown('SIGTERM');
382
413
  });
383
414
  process.on('uncaughtException', (err) => {
384
- error(`[SYSTEM] Uncaught Exception: ${err.message}`);
415
+ error(`[SYSTEM] Uncaught Exception: ${err.message || err}`);
385
416
  handleShutdown('uncaughtException');
386
417
  });
387
418
  process.on('unhandledRejection', (reason) => {
388
- error(`[SYSTEM] Unhandled Rejection: ${reason}`);
419
+ // Handle different types of rejection reasons
420
+ try {
421
+ const message = reason instanceof Error ? reason.message :
422
+ typeof reason === 'object' ? JSON.stringify(reason) :
423
+ String(reason);
424
+ error(`[SYSTEM] Unhandled Rejection: ${message}`);
425
+ // Also log the stack trace if available
426
+ if (reason instanceof Error && reason.stack) {
427
+ console.error('Stack trace:', reason.stack);
428
+ }
429
+ }
430
+ catch (e) {
431
+ error(`[SYSTEM] Unhandled Rejection: [Error converting reason to string]`);
432
+ }
389
433
  handleShutdown('unhandledRejection');
390
434
  });
391
435
  //# sourceMappingURL=proxy.js.map
package/server/shelly.js CHANGED
@@ -102,7 +102,7 @@ class Shelly extends powerbase_1.PowerBase {
102
102
  }
103
103
  }
104
104
  (0, logger_1.debug)("shelly", "meters: " + JSON.stringify(this.meters));
105
- if (this.meters.length) {
105
+ if (this.meters && this.meters.length) {
106
106
  this.timer = setInterval(() => __awaiter(this, void 0, void 0, function* () {
107
107
  this.realtimeCounter += kInterval;
108
108
  // fetch data
@@ -339,7 +339,7 @@ class SmartApp extends webapp_1.WebApp {
339
339
  else {
340
340
  // just show the proxy config
341
341
  }
342
- return this.ejs("proxy", context, { config });
342
+ return this.ejs("proxy", context, { config, connections: proxy_1.gCloudConnections });
343
343
  });
344
344
  }
345
345
  //////////////
@@ -363,36 +363,6 @@ class SmartApp extends webapp_1.WebApp {
363
363
  context.request = "restart";
364
364
  return this.doRestart(false);
365
365
  }
366
- else if (context.action === "install") {
367
- // Network IP configuration - temporarily disabled
368
- // TODO: Re-implement DHCP configuration
369
- message = "Network IP configuration is temporarily disabled. Use 'Save Settings' for mDNS configuration.";
370
- /* Commented out until DHCP implementation is fixed
371
- config = this.scrapeSettings(context);
372
- context.request = "install";
373
- this.write("settings", config);
374
- this.writeDHCP(context, <SettingsConfig>config);
375
- */
376
- }
377
- else if (context.action === "reset") {
378
- // Reset to DHCP - temporarily disabled
379
- // TODO: Re-implement DHCP reset
380
- message = "DHCP reset is temporarily disabled.";
381
- /* Commented out until DHCP implementation is fixed
382
- config = { ...kEmptySettings };
383
- this.resetDHCP(context);
384
- this.write("settings", config);
385
- return this.doReboot(context, false);
386
- */
387
- }
388
- else if (context.action === "reboot") {
389
- // System reboot - temporarily disabled
390
- // TODO: Re-enable after testing
391
- message = "System reboot is temporarily disabled.";
392
- /* Commented out until we want to enable system reboot
393
- return this.doReboot(context, false);
394
- */
395
- }
396
366
  else {
397
367
  // Just display the settings form
398
368
  }
@@ -7,7 +7,7 @@
7
7
  <body>
8
8
  <%- include('/views/nav', this); -%>
9
9
 
10
- <form method="POST" action="/proxy">
10
+ <form method="POST" action="/proxy" style="padding-bottom: 0">
11
11
  <input type="hidden" name="daction" value="save">
12
12
  <h1>
13
13
  Proxy Config
@@ -56,6 +56,78 @@
56
56
  </div>
57
57
 
58
58
  </form>
59
+
60
+ <!-- Connection Status Section -->
61
+ <form>
62
+ <% if (connections && connections.length > 0) { %>
63
+ <table class="striped highlight">
64
+ <thead>
65
+ <tr>
66
+ <th>Cloud Connection</th>
67
+ <th>Device Connection</th>
68
+ <th>Last Attempt</th>
69
+ <th>Last Success</th>
70
+ <th>Retry Count</th>
71
+ <th>Last Error</th>
72
+ </tr>
73
+ </thead>
74
+ <tbody>
75
+ <% connections.forEach(function(conn) { %>
76
+ <tr>
77
+ <td>
78
+ <% if (conn.cloudSocket && conn.cloudSocket.readyState === 'open') { %>
79
+ <span class="green-text">✓ Connected</span><br>
80
+ <small><%= conn.server %>:<%= conn.serverPort %></small>
81
+ <% } else { %>
82
+ <span class="red-text">✗ Disconnected</span>
83
+ <% } %>
84
+ </td>
85
+ <td>
86
+ <% if (conn.deviceStatus.connected) { %>
87
+ <span class="green-text">✓ Connected</span><br>
88
+ <small><%= conn.master %>:<%= conn.masterPort %></small>
89
+ <% } else { %>
90
+ <span class="red-text">✗ Disconnected</span><br>
91
+ <small><%= conn.master %>:<%= conn.masterPort %></small>
92
+ <% } %>
93
+ </td>
94
+ <td>
95
+ <% if (conn.deviceStatus.lastAttempt) { %>
96
+ <%= new Date(conn.deviceStatus.lastAttempt).toLocaleString() %>
97
+ <% } else { %>
98
+ <span class="grey-text">Never</span>
99
+ <% } %>
100
+ </td>
101
+ <td>
102
+ <% if (conn.deviceStatus.lastSuccess) { %>
103
+ <%= new Date(conn.deviceStatus.lastSuccess).toLocaleString() %>
104
+ <% } else { %>
105
+ <span class="grey-text">Never</span>
106
+ <% } %>
107
+ </td>
108
+ <td>
109
+ <% if (conn.deviceStatus.retryCount > 0) { %>
110
+ <span class="orange-text"><%= conn.deviceStatus.retryCount %></span>
111
+ <% } else { %>
112
+ <%= conn.deviceStatus.retryCount %>
113
+ <% } %>
114
+ </td>
115
+ <td>
116
+ <% if (conn.deviceStatus.lastError) { %>
117
+ <span class="red-text"><%= conn.deviceStatus.lastError %></span>
118
+ <% } else { %>
119
+ <span class="grey-text">None</span>
120
+ <% } %>
121
+ </td>
122
+ </tr>
123
+ <% }); %>
124
+ </tbody>
125
+ </table>
126
+ <% } else { %>
127
+ <p class="red-text">No active connections</p>
128
+ <% } %>
129
+ </form>
130
+
59
131
  <%- include('/views/footer'); -%>
60
132
 
61
133
  </body>
package/server/webapp.js CHANGED
@@ -246,6 +246,10 @@ class WebApp extends base_1.Base {
246
246
  return false; // Default: use cached templates for performance
247
247
  }
248
248
  serve(onConnected) {
249
+ // Ensure servers array is initialized (defensive coding)
250
+ if (!this.servers) {
251
+ this.servers = [];
252
+ }
249
253
  // Handle both single port and multiple ports
250
254
  const ports = Array.isArray(this.port) ? this.port : [this.port];
251
255
  (0, logger_1.log)("server", "WebApp - Start serving http on port(s): " + ports.join(", "));
@@ -297,8 +301,12 @@ class WebApp extends base_1.Base {
297
301
  */
298
302
  closeServers() {
299
303
  return __awaiter(this, void 0, void 0, function* () {
304
+ // First, cleanup mDNS to avoid "already in use" errors on restart
305
+ this.destroyMDNS();
300
306
  return new Promise((resolve) => {
301
- if (this.servers.length === 0) {
307
+ // Safety check: servers might be undefined if never initialized
308
+ if (!this.servers || this.servers.length === 0) {
309
+ (0, logger_1.log)("server", "No HTTP servers to close");
302
310
  resolve();
303
311
  return;
304
312
  }
package/.gitattributes DELETED
@@ -1,4 +0,0 @@
1
- *.css linguist-detectable=false
2
- *.map linguist-detectable=false
3
- *.js linguist-detectable=false
4
- *.ts linguist-detectable=true