homebridge-smartsystem 7.0.4 → 7.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/duotecno/types.js +1 -1
- package/package.json +1 -1
- package/server/base.js +4 -0
- package/server/mDNS.js +108 -38
- package/server/platform.js +7 -0
- package/server/proxy.js +99 -44
- package/server/smartapp.js +22 -32
- package/server/views/head.ejs +1 -0
- package/server/views/nav.ejs +16 -8
- package/server/views/proxy.ejs +196 -17
- package/server/views/style.ejs +2 -1
- package/server/webapp.js +7 -0
- 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 -49
- 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 -900
- 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 -223
- package/server/p1.ts +0 -226
- package/server/platform.ts +0 -305
- package/server/powerbase.ts +0 -317
- package/server/proxy.ts +0 -482
- 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 -688
- 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/server/views/nav.ejs
CHANGED
|
@@ -49,15 +49,23 @@
|
|
|
49
49
|
</ul>
|
|
50
50
|
|
|
51
51
|
<% if (notReady) { %>
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
<form>
|
|
53
|
+
<div class="card-panel red">
|
|
54
|
+
<h2>Not ready</h2>
|
|
55
|
+
<div>
|
|
56
|
+
<%= notReadyMessage %>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</form>
|
|
56
60
|
<% } %>
|
|
57
61
|
|
|
58
62
|
<% if (message) { %>
|
|
59
|
-
<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
<form>
|
|
64
|
+
<div class="card-panel green">
|
|
65
|
+
<h2>Message</h2>
|
|
66
|
+
<div>
|
|
67
|
+
<%= message %>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</form>
|
|
63
71
|
<% } %>
|
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
|
|
@@ -16,46 +16,225 @@
|
|
|
16
16
|
<button class="right btn waves-effect waves-light red" type="submit" name="action" value="restart">Restart</button>
|
|
17
17
|
</h1>
|
|
18
18
|
|
|
19
|
-
<
|
|
19
|
+
<h2 style="margin-top: 2rem; font-size: 1.5rem; font-weight: 500;">
|
|
20
|
+
<i class="material-icons left" style="vertical-align: middle;">cloud</i>
|
|
21
|
+
Cloud Server Settings
|
|
22
|
+
</h2>
|
|
23
|
+
|
|
24
|
+
<div class="row" style="margin-top: 1rem;">
|
|
20
25
|
<div class="input-field col s12">
|
|
21
|
-
<
|
|
26
|
+
<label for="cloudServer">Cloud Server Address</label>
|
|
27
|
+
<input placeholder="masters.duotecno.eu" id="cloudServer" name="cloudServer" type="text"
|
|
22
28
|
value="<%= config.cloudServer %>">
|
|
23
|
-
<label for="name">CloudServer</label>
|
|
24
29
|
</div>
|
|
25
30
|
</div>
|
|
31
|
+
|
|
26
32
|
<div class="row">
|
|
27
33
|
<div class="input-field col s12">
|
|
28
|
-
<
|
|
34
|
+
<label for="cloudPort">Cloud Server Port</label>
|
|
35
|
+
<input placeholder="5097" id="cloudPort" name="cloudPort" type="number"
|
|
29
36
|
value="<%= config.cloudPort %>">
|
|
30
|
-
<label for="name">CloudPort</label>
|
|
31
37
|
</div>
|
|
32
38
|
</div>
|
|
33
39
|
|
|
34
|
-
<
|
|
40
|
+
<h2 style="margin-top: 3rem; font-size: 1.5rem; font-weight: 500;">
|
|
41
|
+
<i class="material-icons left" style="vertical-align: middle;">home</i>
|
|
42
|
+
Master Device
|
|
43
|
+
</h2>
|
|
44
|
+
<p class="grey-text" style="margin-bottom: 2rem;">
|
|
45
|
+
Configure your local master device with 2 ports. Gateway port is auto-detected.<br>
|
|
46
|
+
<small>Access gateway via: http://[cloud server]/connect/[unique id]:gateway</small>
|
|
47
|
+
</p>
|
|
48
|
+
|
|
49
|
+
<div class="row" style="margin-top: 1rem;">
|
|
35
50
|
<div class="input-field col s12">
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
<label for="uniqueId">Unique ID (without port)</label>
|
|
52
|
+
<input placeholder="john-doe.duotecno.be" id="uniqueId" name="uniqueId" type="text"
|
|
53
|
+
value="<%= config.uniqueId %>">
|
|
54
|
+
<span class="helper-text">
|
|
55
|
+
This ID is used for both connections:<br>
|
|
56
|
+
• Websocket: [uniqueId]:5001<br>
|
|
57
|
+
• Config API: [uniqueId]:8080<br>
|
|
58
|
+
</span>
|
|
39
59
|
</div>
|
|
40
60
|
</div>
|
|
41
61
|
|
|
42
62
|
<div class="row">
|
|
43
63
|
<div class="input-field col s12">
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
64
|
+
<label for="masterAddress">IP Address</label>
|
|
65
|
+
<input placeholder="192.168.0.97" id="masterAddress" name="masterAddress" type="text"
|
|
66
|
+
value="<%= config.masterAddress %>">
|
|
47
67
|
</div>
|
|
48
68
|
</div>
|
|
49
69
|
|
|
50
70
|
<div class="row">
|
|
51
|
-
<div class="
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
71
|
+
<div class="col s6">
|
|
72
|
+
<p>
|
|
73
|
+
<label>
|
|
74
|
+
<input type="checkbox" class="filled-in" checked disabled />
|
|
75
|
+
<span><strong>Websocket</strong></span>
|
|
76
|
+
</label>
|
|
77
|
+
</p>
|
|
78
|
+
<div class="input-field">
|
|
79
|
+
<input placeholder="5001" id="masterPort" name="masterPort" type="number"
|
|
80
|
+
value="<%= config.masterPort || 5001 %>">
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
<div class="col s6">
|
|
84
|
+
<p>
|
|
85
|
+
<label>
|
|
86
|
+
<input type="checkbox" class="filled-in" checked disabled />
|
|
87
|
+
<span><strong>Config port</strong></span>
|
|
88
|
+
</label>
|
|
89
|
+
</p>
|
|
90
|
+
<div class="input-field">
|
|
91
|
+
<input placeholder="8080" id="masterConfigPort" name="masterConfigPort" type="number"
|
|
92
|
+
value="<%= config.masterConfigPort || 8080 %>">
|
|
93
|
+
</div>
|
|
55
94
|
</div>
|
|
56
95
|
</div>
|
|
57
96
|
|
|
97
|
+
<!-- Hidden fields for compatibility - these are no longer shown in UI -->
|
|
98
|
+
<input type="hidden" name="configPort" value="<%= config.configPort || config.cloudPort %>">
|
|
99
|
+
<input type="hidden" name="configUniqueId" value="<%= config.configUniqueId || '' %>">
|
|
100
|
+
|
|
58
101
|
</form>
|
|
102
|
+
|
|
103
|
+
<!-- mDNS Status Section -->
|
|
104
|
+
<% if (mdnsStatus) { %>
|
|
105
|
+
<form>
|
|
106
|
+
<h1>mDNS Status</h1>
|
|
107
|
+
<% if (mdnsStatus.registrationStatus === 'success') { %>
|
|
108
|
+
<div class="card-panel green lighten-4">
|
|
109
|
+
<span class="green-text text-darken-4">
|
|
110
|
+
<i class="material-icons left">check_circle</i>
|
|
111
|
+
<strong>mDNS Service Registered Successfully</strong>
|
|
112
|
+
</span>
|
|
113
|
+
<p>Hostname: <strong><%= mdnsStatus.registeredName %>.local</strong></p>
|
|
114
|
+
<% if (mdnsStatus.registeredName && !mdnsStatus.registeredName.includes('.')) { %>
|
|
115
|
+
<p class="grey-text">The gateway is accessible on the network as <%= mdnsStatus.registeredName %>.local</p>
|
|
116
|
+
<% } else { %>
|
|
117
|
+
<p class="orange-text">
|
|
118
|
+
<i class="material-icons tiny">warning</i>
|
|
119
|
+
Note: Original name was taken, registered with suffix. Check Settings to change the mDNS name.
|
|
120
|
+
</p>
|
|
121
|
+
<% } %>
|
|
122
|
+
</div>
|
|
123
|
+
<% } else if (mdnsStatus.registrationStatus === 'failed') { %>
|
|
124
|
+
<div class="card-panel red lighten-4">
|
|
125
|
+
<span class="red-text text-darken-4">
|
|
126
|
+
<i class="material-icons left">error</i>
|
|
127
|
+
<strong>mDNS Registration Failed</strong>
|
|
128
|
+
</span>
|
|
129
|
+
<% if (mdnsStatus.registrationError) { %>
|
|
130
|
+
<p>Error: <%= mdnsStatus.registrationError %></p>
|
|
131
|
+
<% } %>
|
|
132
|
+
<p>The gateway is still running, but may not be discoverable via mDNS hostname.</p>
|
|
133
|
+
<p class="grey-text">Solutions:</p>
|
|
134
|
+
<ul class="grey-text">
|
|
135
|
+
<li>Change the mDNS name in <a href="/settings">Settings</a></li>
|
|
136
|
+
<li>Stop other devices/services using the same name</li>
|
|
137
|
+
<li>Access the gateway using IP address instead</li>
|
|
138
|
+
</ul>
|
|
139
|
+
</div>
|
|
140
|
+
<% } else { %>
|
|
141
|
+
<div class="card-panel blue lighten-4">
|
|
142
|
+
<span class="blue-text text-darken-4">
|
|
143
|
+
<i class="material-icons left">info</i>
|
|
144
|
+
mDNS registration is pending...
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
<% } %>
|
|
148
|
+
</form>
|
|
149
|
+
<% } %>
|
|
150
|
+
|
|
151
|
+
<!-- Connection Status Section -->
|
|
152
|
+
<form>
|
|
153
|
+
<% if (connections && connections.length > 0) { %>
|
|
154
|
+
<table class="striped highlight">
|
|
155
|
+
<thead>
|
|
156
|
+
<tr>
|
|
157
|
+
<th>Type</th>
|
|
158
|
+
<th>Unique ID</th>
|
|
159
|
+
<th>Cloud Connection</th>
|
|
160
|
+
<th>Device Connection</th>
|
|
161
|
+
<th>Last Attempt</th>
|
|
162
|
+
<th>Last Success</th>
|
|
163
|
+
<th>Retry Count</th>
|
|
164
|
+
<th>Last Error</th>
|
|
165
|
+
</tr>
|
|
166
|
+
</thead>
|
|
167
|
+
<tbody>
|
|
168
|
+
<% connections.forEach(function(conn) { %>
|
|
169
|
+
<tr>
|
|
170
|
+
<td>
|
|
171
|
+
<% if (conn.isGateway) { %>
|
|
172
|
+
<span class="blue-text"><strong>Gateway</strong></span>
|
|
173
|
+
<% } else { %>
|
|
174
|
+
<span class="teal-text"><strong>Master</strong></span>
|
|
175
|
+
<% } %>
|
|
176
|
+
</td>
|
|
177
|
+
<td>
|
|
178
|
+
<small><%= conn.uniqueId %></small>
|
|
179
|
+
</td>
|
|
180
|
+
<td>
|
|
181
|
+
<% if (conn.cloudSocket && conn.cloudSocket.readyState === 'open') { %>
|
|
182
|
+
<span class="green-text">✓ Connected</span><br>
|
|
183
|
+
<small><%= conn.server %>:<%= conn.serverPort %></small>
|
|
184
|
+
<% } else { %>
|
|
185
|
+
<span class="red-text">✗ Disconnected</span>
|
|
186
|
+
<% } %>
|
|
187
|
+
</td>
|
|
188
|
+
<td>
|
|
189
|
+
<% if (conn.isGateway) { %>
|
|
190
|
+
<span class="grey-text">N/A (HTTP)</span>
|
|
191
|
+
<% } else if (conn.deviceStatus.connected) { %>
|
|
192
|
+
<span class="green-text">✓ Connected</span><br>
|
|
193
|
+
<small><%= conn.master %>:<%= conn.masterPort %></small>
|
|
194
|
+
<% } else if (conn.master) { %>
|
|
195
|
+
<span class="red-text">✗ Disconnected</span><br>
|
|
196
|
+
<small><%= conn.master %>:<%= conn.masterPort %></small>
|
|
197
|
+
<% } else { %>
|
|
198
|
+
<span class="grey-text">Not configured</span>
|
|
199
|
+
<% } %>
|
|
200
|
+
</td>
|
|
201
|
+
<td>
|
|
202
|
+
<% if (conn.deviceStatus.lastAttempt) { %>
|
|
203
|
+
<%= new Date(conn.deviceStatus.lastAttempt).toLocaleString() %>
|
|
204
|
+
<% } else { %>
|
|
205
|
+
<span class="grey-text">Never</span>
|
|
206
|
+
<% } %>
|
|
207
|
+
</td>
|
|
208
|
+
<td>
|
|
209
|
+
<% if (conn.deviceStatus.lastSuccess) { %>
|
|
210
|
+
<%= new Date(conn.deviceStatus.lastSuccess).toLocaleString() %>
|
|
211
|
+
<% } else { %>
|
|
212
|
+
<span class="grey-text">Never</span>
|
|
213
|
+
<% } %>
|
|
214
|
+
</td>
|
|
215
|
+
<td>
|
|
216
|
+
<% if (conn.deviceStatus.retryCount > 0) { %>
|
|
217
|
+
<span class="orange-text"><%= conn.deviceStatus.retryCount %></span>
|
|
218
|
+
<% } else { %>
|
|
219
|
+
<%= conn.deviceStatus.retryCount %>
|
|
220
|
+
<% } %>
|
|
221
|
+
</td>
|
|
222
|
+
<td>
|
|
223
|
+
<% if (conn.deviceStatus.lastError) { %>
|
|
224
|
+
<span class="red-text"><%= conn.deviceStatus.lastError %></span>
|
|
225
|
+
<% } else { %>
|
|
226
|
+
<span class="grey-text">None</span>
|
|
227
|
+
<% } %>
|
|
228
|
+
</td>
|
|
229
|
+
</tr>
|
|
230
|
+
<% }); %>
|
|
231
|
+
</tbody>
|
|
232
|
+
</table>
|
|
233
|
+
<% } else { %>
|
|
234
|
+
<p class="red-text">No active connections</p>
|
|
235
|
+
<% } %>
|
|
236
|
+
</form>
|
|
237
|
+
|
|
59
238
|
<%- include('/views/footer'); -%>
|
|
60
239
|
|
|
61
240
|
</body>
|
package/server/views/style.ejs
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
/* Main content area - grows to fill space */
|
|
7
7
|
body > *:not(nav):not(.page-footer) { flex: 1 0 auto }
|
|
8
8
|
|
|
9
|
-
form { padding-left: 7px; padding-right: 9px; padding-bottom:
|
|
9
|
+
form { padding-left: 7px; padding-right: 9px; padding-bottom: 20px }
|
|
10
|
+
form:last-of-type { padding-bottom: 80px }
|
|
10
11
|
|
|
11
12
|
nav, nav .nav-wrapper { height: 64px !important; line-height: 64px !important; flex-shrink: 0 }
|
|
12
13
|
nav .brand-logo { left: 10px !important; -webkit-transform: none !important; transform: none !important }
|
package/server/webapp.js
CHANGED
|
@@ -346,6 +346,11 @@ class WebApp extends base_1.Base {
|
|
|
346
346
|
*/
|
|
347
347
|
initMDNS(name, version) {
|
|
348
348
|
try {
|
|
349
|
+
// Cleanup any existing mDNS service first (important for hot reload/restart)
|
|
350
|
+
if (this.mdnsService) {
|
|
351
|
+
(0, logger_1.log)("webapp", "Cleaning up existing mDNS service before reinitializing");
|
|
352
|
+
this.destroyMDNS();
|
|
353
|
+
}
|
|
349
354
|
const ports = Array.isArray(this.port) ? this.port : [this.port];
|
|
350
355
|
this.mdnsService = new mDNS_1.MDNSService({
|
|
351
356
|
name,
|
|
@@ -369,6 +374,8 @@ class WebApp extends base_1.Base {
|
|
|
369
374
|
(0, logger_1.err)("webapp", "mDNS service not initialized, cannot register");
|
|
370
375
|
return;
|
|
371
376
|
}
|
|
377
|
+
// The mDNS service now handles conflicts internally with suffix retry
|
|
378
|
+
// No need for additional error handling here
|
|
372
379
|
try {
|
|
373
380
|
this.mdnsService.register();
|
|
374
381
|
}
|
package/.gitattributes
DELETED
package/MIXINS-GUIDE.md
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
# TypeScript Mixins Pattern for SmartApp
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Your SmartApp is getting large because it handles multiple concerns:
|
|
6
|
-
- Device management (WebSocket, power measurements)
|
|
7
|
-
- Link management (Homebridge accessories)
|
|
8
|
-
- Switch management (HTTP switches)
|
|
9
|
-
- Power management (Smappee, P1, Shelly)
|
|
10
|
-
- Proxy configuration
|
|
11
|
-
- Settings management
|
|
12
|
-
|
|
13
|
-
TypeScript doesn't support multiple inheritance, but **mixins** provide a similar capability.
|
|
14
|
-
|
|
15
|
-
## Solution Options
|
|
16
|
-
|
|
17
|
-
### **Option 1: Mixin Pattern** (Most Flexible)
|
|
18
|
-
|
|
19
|
-
Create separate mixin functions that add functionality to your class:
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
// server/mixins/DeviceMixin.ts
|
|
23
|
-
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
24
|
-
|
|
25
|
-
export interface DeviceMixinBase {
|
|
26
|
-
devices: Array<Device>;
|
|
27
|
-
system: System;
|
|
28
|
-
power: Power;
|
|
29
|
-
// ... other required properties
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function DeviceMixin<TBase extends Constructor<DeviceMixinBase>>(Base: TBase) {
|
|
33
|
-
return class extends Base {
|
|
34
|
-
setupWebSocketServers() { /* ... */ }
|
|
35
|
-
sendDeviceList(ws: WebSocket) { /* ... */ }
|
|
36
|
-
getDeviceList() { /* ... */ }
|
|
37
|
-
// ... all device-related methods
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Usage in smartapp.ts
|
|
42
|
-
import { DeviceMixin } from './mixins/DeviceMixin';
|
|
43
|
-
import { LinkMixin } from './mixins/LinkMixin';
|
|
44
|
-
import { SwitchMixin } from './mixins/SwitchMixin';
|
|
45
|
-
|
|
46
|
-
// Apply mixins
|
|
47
|
-
const SmartAppBase = SwitchMixin(LinkMixin(DeviceMixin(WebApp)));
|
|
48
|
-
|
|
49
|
-
export class SmartApp extends SmartAppBase {
|
|
50
|
-
// Only SmartApp-specific logic here
|
|
51
|
-
constructor(system: System, power: Power, platform: Platform) {
|
|
52
|
-
super("smartapp");
|
|
53
|
-
// ...
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
**Pros:**
|
|
59
|
-
- True composition - each mixin is independent
|
|
60
|
-
- Can be reused across different classes
|
|
61
|
-
- TypeScript type-safe
|
|
62
|
-
- Easy to test mixins individually
|
|
63
|
-
|
|
64
|
-
**Cons:**
|
|
65
|
-
- Requires some boilerplate
|
|
66
|
-
- Mixing order matters
|
|
67
|
-
- Can be confusing at first
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
### **Option 2: Delegate Pattern** (Simpler)
|
|
72
|
-
|
|
73
|
-
Create separate service/manager classes and delegate to them:
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
// server/services/DeviceManager.ts
|
|
77
|
-
export class DeviceManager {
|
|
78
|
-
constructor(
|
|
79
|
-
private devices: Array<Device>,
|
|
80
|
-
private system: System,
|
|
81
|
-
private power: Power
|
|
82
|
-
) {}
|
|
83
|
-
|
|
84
|
-
setupWebSocketServers(servers: http.Server[]) { /* ... */ }
|
|
85
|
-
getDeviceList() { /* ... */ }
|
|
86
|
-
// ... all device methods
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// server/services/LinkManager.ts
|
|
90
|
-
export class LinkManager {
|
|
91
|
-
constructor(
|
|
92
|
-
private links: Array<Link>,
|
|
93
|
-
private system: System
|
|
94
|
-
) {}
|
|
95
|
-
|
|
96
|
-
initLinks() { /* ... */ }
|
|
97
|
-
updateLink(inx: number, link: Link) { /* ... */ }
|
|
98
|
-
// ... all link methods
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// In smartapp.ts
|
|
102
|
-
export class SmartApp extends WebApp {
|
|
103
|
-
private deviceManager: DeviceManager;
|
|
104
|
-
private linkManager: LinkManager;
|
|
105
|
-
|
|
106
|
-
constructor(system: System, power: Power, platform: Platform) {
|
|
107
|
-
super("smartapp");
|
|
108
|
-
|
|
109
|
-
this.deviceManager = new DeviceManager(this.devices, this.system, this.power);
|
|
110
|
-
this.linkManager = new LinkManager(this.links, this.system);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Delegate to managers
|
|
114
|
-
setupWebSocketServers() {
|
|
115
|
-
this.deviceManager.setupWebSocketServers(this.servers);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
initLinks() {
|
|
119
|
-
this.linkManager.initLinks();
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
**Pros:**
|
|
125
|
-
- Simple and intuitive
|
|
126
|
-
- Easy to understand
|
|
127
|
-
- Good separation of concerns
|
|
128
|
-
- Easy to unit test
|
|
129
|
-
|
|
130
|
-
**Cons:**
|
|
131
|
-
- More delegation boilerplate
|
|
132
|
-
- Not as "clean" as mixins
|
|
133
|
-
- Managers need access to SmartApp state
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
### **Option 3: Module Pattern** (Most Practical)
|
|
138
|
-
|
|
139
|
-
Split into separate modules but keep in same class:
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
// server/smartapp/devices.ts
|
|
143
|
-
export class DeviceManagement {
|
|
144
|
-
static setupWebSocketServers(app: SmartApp) { /* ... */ }
|
|
145
|
-
static getDeviceList(app: SmartApp) { /* ... */ }
|
|
146
|
-
// ... all device methods as static functions
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// server/smartapp/links.ts
|
|
150
|
-
export class LinkManagement {
|
|
151
|
-
static initLinks(app: SmartApp) { /* ... */ }
|
|
152
|
-
static updateLink(app: SmartApp, inx: number, link: Link) { /* ... */ }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// In smartapp.ts
|
|
156
|
-
import { DeviceManagement } from './smartapp/devices';
|
|
157
|
-
import { LinkManagement } from './smartapp/links';
|
|
158
|
-
|
|
159
|
-
export class SmartApp extends WebApp {
|
|
160
|
-
setupWebSocketServers() {
|
|
161
|
-
DeviceManagement.setupWebSocketServers(this);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
initLinks() {
|
|
165
|
-
LinkManagement.initLinks(this);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
**Pros:**
|
|
171
|
-
- Simple to implement
|
|
172
|
-
- Code is organized in separate files
|
|
173
|
-
- No complex patterns needed
|
|
174
|
-
- Easy to refactor incrementally
|
|
175
|
-
|
|
176
|
-
**Cons:**
|
|
177
|
-
- Still some methods in main class
|
|
178
|
-
- Not true composition
|
|
179
|
-
- Methods take `app` parameter
|
|
180
|
-
|
|
181
|
-
---
|
|
182
|
-
|
|
183
|
-
## **Recommended Approach**
|
|
184
|
-
|
|
185
|
-
For your situation, I'd recommend **Option 2 (Delegate Pattern)** or **Option 3 (Module Pattern)**.
|
|
186
|
-
|
|
187
|
-
Here's why:
|
|
188
|
-
1. **Easier to understand** - no complex mixin magic
|
|
189
|
-
2. **Incremental refactoring** - move one feature at a time
|
|
190
|
-
3. **Better testability** - each manager/module is isolated
|
|
191
|
-
4. **Clearer dependencies** - you see what each part needs
|
|
192
|
-
|
|
193
|
-
### Quick Win: Module Pattern
|
|
194
|
-
|
|
195
|
-
Split your 1680-line smartapp.ts into:
|
|
196
|
-
|
|
197
|
-
```
|
|
198
|
-
server/
|
|
199
|
-
smartapp.ts (main class, ~300 lines)
|
|
200
|
-
smartapp/
|
|
201
|
-
devices.ts (WebSocket & power, ~300 lines)
|
|
202
|
-
links.ts (Homebridge links, ~200 lines)
|
|
203
|
-
switches.ts (HTTP switches, ~200 lines)
|
|
204
|
-
power.ts (Power bindings, ~150 lines)
|
|
205
|
-
proxy.ts (Proxy config, ~150 lines)
|
|
206
|
-
routes.ts (HTTP routing, ~200 lines)
|
|
207
|
-
units.ts (Unit management, ~150 lines)
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
Would you like me to help you refactor SmartApp using one of these patterns?
|