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.
- package/README.md +57 -1
- package/duotecno/types.js +2 -1
- package/package.json +1 -1
- package/server/mDNS.js +44 -15
- package/server/platform.js +7 -0
- package/server/powerbase.js +11 -8
- package/server/proxy.js +69 -25
- package/server/shelly.js +1 -1
- package/server/smartapp.js +1 -31
- package/server/views/proxy.ejs +73 -1
- package/server/webapp.js +9 -1
- package/.gitattributes +0 -4
- package/MIXINS-GUIDE.md +0 -210
- package/SETTINGS-UPDATE.md +0 -195
- package/TESTING-GUIDE.md +0 -220
- package/WEBSOCKET-API.md +0 -303
- package/accessories/accessory.ts +0 -128
- package/accessories/bulb.ts +0 -38
- package/accessories/dimmer.ts +0 -97
- package/accessories/door.ts +0 -88
- package/accessories/garagedoor.ts +0 -134
- package/accessories/lock.ts +0 -103
- package/accessories/mood.ts +0 -68
- package/accessories/switch.ts +0 -34
- package/accessories/temperature.ts +0 -55
- package/accessories/windowcovering.ts +0 -189
- package/config.homebridge.json +0 -23
- package/config.json +0 -509
- package/duotecno/Q.ts +0 -56
- package/duotecno/api.ts +0 -63
- package/duotecno/colors.ts +0 -96
- package/duotecno/config.ts +0 -125
- package/duotecno/logger.ts +0 -43
- package/duotecno/master.ts +0 -884
- package/duotecno/protocol.ts +0 -992
- package/duotecno/smartsocket.ts +0 -118
- package/duotecno/system.ts +0 -439
- package/duotecno/types.ts +0 -899
- package/index.ts +0 -9
- package/log.txt +0 -74
- package/raspberry-pi-setup.sh +0 -80
- package/server/HA-API.ts +0 -102
- package/server/HB.ts +0 -343
- package/server/base.ts +0 -54
- package/server/mDNS.ts +0 -198
- package/server/p1.ts +0 -226
- package/server/platform.ts +0 -305
- package/server/powerbase.ts +0 -315
- package/server/proxy.ts +0 -466
- package/server/shelly.ts +0 -212
- package/server/smappee.ts +0 -309
- package/server/smartapp.ts +0 -1710
- package/server/socapp.ts +0 -182
- package/server/somfy.ts +0 -174
- package/server/support.ts +0 -142
- package/server/webapp.ts +0 -678
- package/testHB.js +0 -41
- package/testHB.ts +0 -54
- package/testProxy.js +0 -7
- package/testProxy.ts +0 -6
- package/testWS.js +0 -137
- package/testWS.ts +0 -174
- package/update.sh +0 -16
- 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.
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
(
|
|
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
|
/**
|
package/server/platform.js
CHANGED
|
@@ -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
|
package/server/powerbase.js
CHANGED
|
@@ -79,16 +79,17 @@ class PowerBase extends base_1.Base {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
evalPower(exp) {
|
|
82
|
-
var _a, _b, _c, _d
|
|
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 -=
|
|
89
|
+
result -= channelPower;
|
|
88
90
|
else if (parsed[i] === "+")
|
|
89
|
-
result +=
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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.
|
|
349
|
-
|
|
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
|
|
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]
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
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
|
package/server/smartapp.js
CHANGED
|
@@ -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
|
}
|
package/server/views/proxy.ejs
CHANGED
|
@@ -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
|
-
|
|
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