homebridge-zwave-usb 2.1.0 → 2.2.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 +10 -0
- package/dist/platform/ZWaveUsbPlatform.js +28 -0
- package/dist/zwave/ZWaveController.d.ts +5 -0
- package/dist/zwave/ZWaveController.js +12 -0
- package/dist/zwave/interfaces.d.ts +1 -0
- package/homebridge-ui/public/index.html +27 -2
- package/homebridge-ui/server.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ A high-performance, production-grade [Homebridge](https://homebridge.io) integra
|
|
|
15
15
|
- **Reactive Architecture**: Real-time status updates and automatic hardware recovery (hot-plugging support).
|
|
16
16
|
- **Advanced Metadata Repair**: Automatically cleans up obsolete HomeKit services/characteristics during network updates.
|
|
17
17
|
- **Log Piping**: Internal Z-Wave JS driver logs are piped directly into the Homebridge terminal for seamless debugging.
|
|
18
|
+
- **OTA Firmware Updates**: Check for and install official manufacturer firmware updates directly from the Homebridge UI.
|
|
18
19
|
|
|
19
20
|
---
|
|
20
21
|
|
|
@@ -136,6 +137,15 @@ The plugin provides additional tools for network maintenance:
|
|
|
136
137
|
- **Dual S2 PIN Entry**: When a device requires a PIN, you can enter it via the Homebridge terminal (`echo "12345" > s2_pin.txt`) or via the `S2 PIN Entry` characteristic in third-party HomeKit apps.
|
|
137
138
|
- **Automated Reconciliation**: Orphaned accessories are automatically removed from HomeKit 60 seconds after startup if the node is no longer present in the Z-Wave network.
|
|
138
139
|
|
|
140
|
+
## 🛠️ Maintenance & Firmware Updates
|
|
141
|
+
|
|
142
|
+
The plugin includes a dedicated **Maintenance** tab within the Homebridge custom UI (accessible via the plugin settings).
|
|
143
|
+
|
|
144
|
+
- **Node Overview**: View a complete list of all Z-Wave nodes in your network, including their status and current firmware versions.
|
|
145
|
+
- **Official Updates**: Click "Check Update" to query the official Z-Wave JS Firmware Update Service. If a manufacturer-approved update is available, you can start the OTA transfer with a single click.
|
|
146
|
+
- **Battery Device Support**: Firmware updates are supported for battery-powered devices. You will be prompted to manually wake the device after starting the process to initiate the transfer.
|
|
147
|
+
- **Real-time Progress**: Monitor the progress of the firmware transfer via a live progress bar.
|
|
148
|
+
|
|
139
149
|
## ❓ Troubleshooting
|
|
140
150
|
|
|
141
151
|
- **Device showing "No Response"?** Check the Homebridge logs. If the device is battery-powered, it may need to be "woken up" (usually by pressing a physical button on the device) to complete the interview.
|
|
@@ -125,6 +125,34 @@ class ZWaveUsbPlatform {
|
|
|
125
125
|
}));
|
|
126
126
|
return sendJson(nodes);
|
|
127
127
|
}
|
|
128
|
+
if (normalizedUrl.startsWith('/nodes/') && normalizedUrl.endsWith('/name') && method === 'POST') {
|
|
129
|
+
let body = '';
|
|
130
|
+
req.on('data', (chunk) => (body += chunk));
|
|
131
|
+
req.on('end', () => {
|
|
132
|
+
try {
|
|
133
|
+
const parts = normalizedUrl.split('/');
|
|
134
|
+
const nodeId = parseInt(parts[2], 10);
|
|
135
|
+
const { name } = JSON.parse(body);
|
|
136
|
+
if (!this.zwaveController) {
|
|
137
|
+
throw new Error('Controller not initialized');
|
|
138
|
+
}
|
|
139
|
+
this.zwaveController.setNodeName(nodeId, name);
|
|
140
|
+
// Update Homebridge Accessory Name
|
|
141
|
+
const accessory = this.zwaveAccessories.get(nodeId);
|
|
142
|
+
if (accessory) {
|
|
143
|
+
this.log.info(`Renaming HomeKit accessory for Node ${nodeId} to: ${name}`);
|
|
144
|
+
accessory.platformAccessory.displayName = name;
|
|
145
|
+
this.api.updatePlatformAccessories([accessory.platformAccessory]);
|
|
146
|
+
}
|
|
147
|
+
return sendJson({ success: true });
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
this.log.error(`Failed to rename node: ${err}`);
|
|
151
|
+
return sendJson({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
128
156
|
if (normalizedUrl.startsWith('/firmware/updates/') && method === 'GET') {
|
|
129
157
|
const nodeId = parseInt(normalizedUrl.split('/').pop() || '0', 10);
|
|
130
158
|
this.zwaveController?.getAvailableFirmwareUpdates(nodeId)
|
|
@@ -54,6 +54,11 @@ export declare class ZWaveController extends EventEmitter implements IZWaveContr
|
|
|
54
54
|
* that can no longer be physically excluded.
|
|
55
55
|
*/
|
|
56
56
|
removeFailedNode(nodeId: number): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Updates the user-defined name for a node in the Z-Wave network.
|
|
59
|
+
* This name is persisted by Z-Wave JS in its cache files.
|
|
60
|
+
*/
|
|
61
|
+
setNodeName(nodeId: number, name: string): void;
|
|
57
62
|
getAvailableFirmwareUpdates(nodeId: number): Promise<unknown[]>;
|
|
58
63
|
beginFirmwareUpdate(nodeId: number, update: unknown): Promise<void>;
|
|
59
64
|
abortFirmwareUpdate(nodeId: number): Promise<void>;
|
|
@@ -670,6 +670,18 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
670
670
|
throw err;
|
|
671
671
|
}
|
|
672
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Updates the user-defined name for a node in the Z-Wave network.
|
|
675
|
+
* This name is persisted by Z-Wave JS in its cache files.
|
|
676
|
+
*/
|
|
677
|
+
setNodeName(nodeId, name) {
|
|
678
|
+
const node = this.nodes.get(nodeId);
|
|
679
|
+
if (!node) {
|
|
680
|
+
throw new Error(`Node ${nodeId} not found`);
|
|
681
|
+
}
|
|
682
|
+
this.log.info(`Updating name for Node ${nodeId} to: "${name}"`);
|
|
683
|
+
node.name = name;
|
|
684
|
+
}
|
|
673
685
|
async getAvailableFirmwareUpdates(nodeId) {
|
|
674
686
|
if (nodeId === 1) {
|
|
675
687
|
this.log.debug('Node 1 is the controller; skipping firmware update check.');
|
|
@@ -20,6 +20,7 @@ export interface IZWaveController extends EventEmitter {
|
|
|
20
20
|
startHealing(): Promise<boolean>;
|
|
21
21
|
stopHealing(): Promise<boolean>;
|
|
22
22
|
removeFailedNode(nodeId: number): Promise<void>;
|
|
23
|
+
setNodeName(nodeId: number, name: string): void;
|
|
23
24
|
setS2Pin(pin: string): void;
|
|
24
25
|
getAvailableFirmwareUpdates(nodeId: number): Promise<unknown[]>;
|
|
25
26
|
beginFirmwareUpdate(nodeId: number, update: unknown): Promise<void>;
|
|
@@ -172,6 +172,7 @@
|
|
|
172
172
|
const tr = document.createElement('tr');
|
|
173
173
|
const status = statusMap[node.status] || 'Unknown';
|
|
174
174
|
const progress = node.firmwareProgress;
|
|
175
|
+
const nodeName = node.name || node.label || 'Node ' + node.nodeId;
|
|
175
176
|
|
|
176
177
|
let actionHtml = '';
|
|
177
178
|
if (progress) {
|
|
@@ -184,12 +185,17 @@
|
|
|
184
185
|
} else if (node.nodeId === 1) {
|
|
185
186
|
actionHtml = `<span class="badge badge-light">Controller</span>`;
|
|
186
187
|
} else {
|
|
187
|
-
actionHtml =
|
|
188
|
+
actionHtml = `
|
|
189
|
+
<div class="btn-group">
|
|
190
|
+
<button class="btn btn-sm btn-outline-secondary rename-node" data-nodeid="${node.nodeId}" data-name="${nodeName}">Rename</button>
|
|
191
|
+
<button class="btn btn-sm btn-outline-primary check-update" data-nodeid="${node.nodeId}">Check Update</button>
|
|
192
|
+
</div>
|
|
193
|
+
`;
|
|
188
194
|
}
|
|
189
195
|
|
|
190
196
|
tr.innerHTML = `
|
|
191
197
|
<td>${node.nodeId}</td>
|
|
192
|
-
<td
|
|
198
|
+
<td id="node-name-${node.nodeId}">${nodeName}</td>
|
|
193
199
|
<td><span class="badge badge-${status === 'Alive' || status === 'Awake' ? 'success' : 'secondary'}">${status}</span></td>
|
|
194
200
|
<td>${node.firmwareVersion || 'Unknown'}</td>
|
|
195
201
|
<td id="node-action-${node.nodeId}">${actionHtml}</td>
|
|
@@ -197,6 +203,25 @@
|
|
|
197
203
|
tbody.appendChild(tr);
|
|
198
204
|
});
|
|
199
205
|
|
|
206
|
+
// Add event listeners for rename buttons
|
|
207
|
+
document.querySelectorAll('.rename-node').forEach((btn) => {
|
|
208
|
+
btn.addEventListener('click', async (e) => {
|
|
209
|
+
const nodeId = e.target.getAttribute('data-nodeid');
|
|
210
|
+
const currentName = e.target.getAttribute('data-name');
|
|
211
|
+
const newName = prompt(`Enter new name for Node ${nodeId}:`, currentName);
|
|
212
|
+
|
|
213
|
+
if (newName !== null && newName !== currentName && newName.trim() !== '') {
|
|
214
|
+
try {
|
|
215
|
+
await window.homebridge.request('rename-node', { nodeId, name: newName.trim() });
|
|
216
|
+
window.homebridge.toast.success(`Node ${nodeId} renamed to ${newName}`);
|
|
217
|
+
loadNodes();
|
|
218
|
+
} catch (err) {
|
|
219
|
+
window.homebridge.toast.error(err.message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
200
225
|
// Add event listeners for check buttons
|
|
201
226
|
document.querySelectorAll('.check-update').forEach((btn) => {
|
|
202
227
|
btn.addEventListener('click', async (e) => {
|
package/homebridge-ui/server.js
CHANGED
|
@@ -11,6 +11,10 @@ class UiServer extends HomebridgePluginUiServer {
|
|
|
11
11
|
return await this.ipcRequest('/nodes', 'GET');
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
+
this.onRequest('rename-node', async ({ nodeId, name }) => {
|
|
15
|
+
return await this.ipcRequest(`/nodes/${nodeId}/name`, 'POST', { name });
|
|
16
|
+
});
|
|
17
|
+
|
|
14
18
|
this.onRequest('check-firmware', async (nodeId) => {
|
|
15
19
|
return await this.ipcRequest(`/firmware/updates/${nodeId}`, 'GET');
|
|
16
20
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-zwave-usb",
|
|
3
3
|
"displayName": "Homebridge Z-Wave USB",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.2.0",
|
|
5
5
|
"description": "A Homebridge dynamic platform plugin for Z-Wave USB controllers using zwave-js.",
|
|
6
6
|
"license": "Polyform-Noncommercial-1.0.0",
|
|
7
7
|
"funding": {
|