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.
Files changed (66) hide show
  1. package/README.md +2 -0
  2. package/duotecno/types.js +1 -1
  3. package/package.json +1 -1
  4. package/server/base.js +4 -0
  5. package/server/mDNS.js +108 -38
  6. package/server/platform.js +7 -0
  7. package/server/proxy.js +99 -44
  8. package/server/smartapp.js +22 -32
  9. package/server/views/head.ejs +1 -0
  10. package/server/views/nav.ejs +16 -8
  11. package/server/views/proxy.ejs +196 -17
  12. package/server/views/style.ejs +2 -1
  13. package/server/webapp.js +7 -0
  14. package/.gitattributes +0 -4
  15. package/MIXINS-GUIDE.md +0 -210
  16. package/SETTINGS-UPDATE.md +0 -195
  17. package/TESTING-GUIDE.md +0 -220
  18. package/WEBSOCKET-API.md +0 -303
  19. package/accessories/accessory.ts +0 -128
  20. package/accessories/bulb.ts +0 -38
  21. package/accessories/dimmer.ts +0 -97
  22. package/accessories/door.ts +0 -88
  23. package/accessories/garagedoor.ts +0 -134
  24. package/accessories/lock.ts +0 -103
  25. package/accessories/mood.ts +0 -68
  26. package/accessories/switch.ts +0 -34
  27. package/accessories/temperature.ts +0 -55
  28. package/accessories/windowcovering.ts +0 -189
  29. package/config.homebridge.json +0 -23
  30. package/config.json +0 -49
  31. package/duotecno/Q.ts +0 -56
  32. package/duotecno/api.ts +0 -63
  33. package/duotecno/colors.ts +0 -96
  34. package/duotecno/config.ts +0 -125
  35. package/duotecno/logger.ts +0 -43
  36. package/duotecno/master.ts +0 -884
  37. package/duotecno/protocol.ts +0 -992
  38. package/duotecno/smartsocket.ts +0 -118
  39. package/duotecno/system.ts +0 -439
  40. package/duotecno/types.ts +0 -900
  41. package/index.ts +0 -9
  42. package/log.txt +0 -74
  43. package/raspberry-pi-setup.sh +0 -80
  44. package/server/HA-API.ts +0 -102
  45. package/server/HB.ts +0 -343
  46. package/server/base.ts +0 -54
  47. package/server/mDNS.ts +0 -223
  48. package/server/p1.ts +0 -226
  49. package/server/platform.ts +0 -305
  50. package/server/powerbase.ts +0 -317
  51. package/server/proxy.ts +0 -482
  52. package/server/shelly.ts +0 -212
  53. package/server/smappee.ts +0 -309
  54. package/server/smartapp.ts +0 -1710
  55. package/server/socapp.ts +0 -182
  56. package/server/somfy.ts +0 -174
  57. package/server/support.ts +0 -142
  58. package/server/webapp.ts +0 -688
  59. package/testHB.js +0 -41
  60. package/testHB.ts +0 -54
  61. package/testProxy.js +0 -7
  62. package/testProxy.ts +0 -6
  63. package/testWS.js +0 -137
  64. package/testWS.ts +0 -174
  65. package/update.sh +0 -16
  66. package/updserv.sh +0 -13
@@ -49,15 +49,23 @@
49
49
  </ul>
50
50
 
51
51
  <% if (notReady) { %>
52
- <h2>Not ready</h2>
53
- <div>
54
- <%= notReadyMessage %>
55
- </div>
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
- <h2>Message</h2>
60
- <div>
61
- <%= message %>
62
- </div>
63
+ <form>
64
+ <div class="card-panel green">
65
+ <h2>Message</h2>
66
+ <div>
67
+ <%= message %>
68
+ </div>
69
+ </div>
70
+ </form>
63
71
  <% } %>
@@ -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
- <div class="row">
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
- <input placeholder="ws.duotecno.eu" id="cloudServer" name="cloudServer" type="text"
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
- <input placeholder="5099" id="cloudPort" name="cloudPort" type="number"
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
- <div class="row">
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
- <input placeholder="192.168.0.2" id="masterAddress" name="masterAddress" type="text"
37
- value="<%= config.masterAddress %>">
38
- <label for="name">MasterAddress</label>
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
- <input placeholder="5001" id="masterPort" name="masterPort" type="text"
45
- value="<%= config.masterPort %>">
46
- <label for="name">MasterPort</label>
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="input-field col s12">
52
- <input placeholder="client:5002" id="uniqueId" name="uniqueId" type="text"
53
- value="<%= config.uniqueId %>">
54
- <label for="name">UniqueId</label>
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>
@@ -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: 80px }
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
@@ -1,4 +0,0 @@
1
- *.css linguist-detectable=false
2
- *.map linguist-detectable=false
3
- *.js linguist-detectable=false
4
- *.ts linguist-detectable=true
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?