homebridge-zwave-usb 1.2.2 → 1.2.5
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/dist/accessories/ControllerAccessory.js +6 -1
- package/dist/accessories/ZWaveAccessory.js +7 -0
- package/dist/features/BatteryFeature.js +1 -1
- package/dist/features/MultilevelSwitchFeature.d.ts +1 -0
- package/dist/features/MultilevelSwitchFeature.js +7 -4
- package/dist/platform/ZWaveUsbPlatform.js +24 -7
- package/dist/zwave/ZWaveController.js +11 -1
- package/dist/zwave/interfaces.d.ts +11 -9
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ class ControllerAccessory {
|
|
|
14
14
|
this.platform = platform;
|
|
15
15
|
this.controller = controller;
|
|
16
16
|
const homeId = this.controller.homeId;
|
|
17
|
-
const uuid = this.platform.api.hap.uuid.generate(`
|
|
17
|
+
const uuid = this.platform.api.hap.uuid.generate(`homebridge-zwave-usb-controller-${homeId}`);
|
|
18
18
|
const existingAccessory = this.platform.accessories.find((accessory) => accessory.UUID === uuid);
|
|
19
19
|
if (existingAccessory) {
|
|
20
20
|
this.platformAccessory = existingAccessory;
|
|
@@ -25,6 +25,11 @@ class ControllerAccessory {
|
|
|
25
25
|
this.platform.api.registerPlatformAccessories('homebridge-zwave-usb', 'ZWaveUSB', [this.platformAccessory]);
|
|
26
26
|
this.platform.accessories.push(this.platformAccessory);
|
|
27
27
|
}
|
|
28
|
+
// Set accessory information
|
|
29
|
+
this.platformAccessory.getService(this.platform.Service.AccessoryInformation)
|
|
30
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'Aeotec / Z-Wave JS')
|
|
31
|
+
.setCharacteristic(this.platform.Characteristic.Model, 'Z-Wave USB Controller')
|
|
32
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, homeId?.toString() || 'Unknown');
|
|
28
33
|
this.inclusionService =
|
|
29
34
|
this.platformAccessory.getService('Inclusion Mode') ||
|
|
30
35
|
this.platformAccessory.addService(this.platform.Service.Switch, 'Inclusion Mode', 'Inclusion');
|
|
@@ -24,6 +24,13 @@ class ZWaveAccessory {
|
|
|
24
24
|
this.platform.api.registerPlatformAccessories('homebridge-zwave-usb', 'ZWaveUSB', [this.platformAccessory]);
|
|
25
25
|
this.platform.accessories.push(this.platformAccessory);
|
|
26
26
|
}
|
|
27
|
+
// Set accessory information
|
|
28
|
+
const manufacturer = this.node.deviceConfig?.manufacturer || 'Unknown';
|
|
29
|
+
const model = this.node.deviceConfig?.label || `Node ${this.node.nodeId}`;
|
|
30
|
+
this.platformAccessory.getService(this.platform.Service.AccessoryInformation)
|
|
31
|
+
.setCharacteristic(this.platform.Characteristic.Manufacturer, manufacturer)
|
|
32
|
+
.setCharacteristic(this.platform.Characteristic.Model, model)
|
|
33
|
+
.setCharacteristic(this.platform.Characteristic.SerialNumber, `Node ${this.node.nodeId}`);
|
|
27
34
|
}
|
|
28
35
|
addFeature(feature) {
|
|
29
36
|
this.features.push(feature);
|
|
@@ -26,7 +26,7 @@ class BatteryFeature extends ZWaveFeature_1.BaseFeature {
|
|
|
26
26
|
property: 'level',
|
|
27
27
|
endpoint: this.endpoint.index,
|
|
28
28
|
});
|
|
29
|
-
return typeof value === 'number' ? value :
|
|
29
|
+
return typeof value === 'number' ? Math.max(0, Math.min(value, 100)) : -1;
|
|
30
30
|
}
|
|
31
31
|
getStatusLowBattery() {
|
|
32
32
|
const value = this.node.getValue({
|
|
@@ -4,6 +4,7 @@ exports.MultilevelSwitchFeature = void 0;
|
|
|
4
4
|
const ZWaveFeature_1 = require("./ZWaveFeature");
|
|
5
5
|
class MultilevelSwitchFeature extends ZWaveFeature_1.BaseFeature {
|
|
6
6
|
service;
|
|
7
|
+
lastKnownBrightness = 0;
|
|
7
8
|
init() {
|
|
8
9
|
const subType = this.endpoint.index.toString();
|
|
9
10
|
this.service = this.getService(this.platform.Service.Lightbulb, undefined, subType);
|
|
@@ -25,13 +26,15 @@ class MultilevelSwitchFeature extends ZWaveFeature_1.BaseFeature {
|
|
|
25
26
|
if (typeof value === 'number') {
|
|
26
27
|
const isOn = value > 0;
|
|
27
28
|
this.service.updateCharacteristic(this.platform.Characteristic.On, isOn);
|
|
28
|
-
if (
|
|
29
|
-
|
|
29
|
+
if (value <= 99) {
|
|
30
|
+
this.lastKnownBrightness = value;
|
|
30
31
|
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, value);
|
|
31
32
|
}
|
|
32
|
-
else if (
|
|
33
|
+
else if (value === 255 && isOn) {
|
|
33
34
|
// 255 = restore previous level
|
|
34
|
-
//
|
|
35
|
+
// Use our tracked brightness, default to 100 if unknown
|
|
36
|
+
const brightness = this.lastKnownBrightness > 0 ? this.lastKnownBrightness : 100;
|
|
37
|
+
this.service.updateCharacteristic(this.platform.Characteristic.Brightness, brightness);
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
}
|
|
@@ -68,18 +68,31 @@ class ZWaveUsbPlatform {
|
|
|
68
68
|
for (const acc of this.zwaveAccessories.values()) {
|
|
69
69
|
managedUuids.add(acc.platformAccessory.UUID);
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
this.log.debug(`Reconciliation: ${managedUuids.size} managed accessories, ${this.accessories.length} in cache.`);
|
|
72
|
+
const orphaned = this.accessories.filter(acc => {
|
|
73
|
+
const isOrphaned = !managedUuids.has(acc.UUID);
|
|
74
|
+
if (isOrphaned) {
|
|
75
|
+
this.log.info(`Found orphaned accessory in cache: ${acc.displayName} (${acc.UUID})`);
|
|
76
|
+
}
|
|
77
|
+
return isOrphaned;
|
|
78
|
+
});
|
|
72
79
|
if (orphaned.length > 0) {
|
|
73
80
|
this.log.info(`Removing ${orphaned.length} orphaned accessories from cache...`);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
try {
|
|
82
|
+
this.api.unregisterPlatformAccessories('homebridge-zwave-usb', 'ZWaveUSB', orphaned);
|
|
83
|
+
for (const orphan of orphaned) {
|
|
84
|
+
const index = this.accessories.indexOf(orphan);
|
|
85
|
+
if (index !== -1) {
|
|
86
|
+
this.accessories.splice(index, 1);
|
|
87
|
+
}
|
|
79
88
|
}
|
|
89
|
+
this.log.info('Successfully removed orphaned accessories.');
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
this.log.error('Failed to unregister orphaned accessories:', err);
|
|
80
93
|
}
|
|
81
94
|
}
|
|
82
|
-
},
|
|
95
|
+
}, 10000);
|
|
83
96
|
}
|
|
84
97
|
catch (err) {
|
|
85
98
|
this.log.error('Failed to connect to Z-Wave controller:', err);
|
|
@@ -90,6 +103,10 @@ class ZWaveUsbPlatform {
|
|
|
90
103
|
}
|
|
91
104
|
handleNodeReady(node) {
|
|
92
105
|
this.log.info(`Node ${node.nodeId} ready`);
|
|
106
|
+
if (!node.ready) {
|
|
107
|
+
this.log.warn(`Node ${node.nodeId} reported ready but node.ready is false, skipping...`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
93
110
|
const homeId = this.zwaveController?.homeId;
|
|
94
111
|
if (!homeId) {
|
|
95
112
|
this.log.error(`Cannot create accessory for Node ${node.nodeId} - Home ID not available`);
|
|
@@ -34,7 +34,7 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
34
34
|
}
|
|
35
35
|
// Redirect Z-Wave JS logs to Homebridge
|
|
36
36
|
const customLogTransport = {
|
|
37
|
-
|
|
37
|
+
log: (info, next) => {
|
|
38
38
|
const message = info.message || info;
|
|
39
39
|
const label = info.label ? `[${info.label}] ` : '';
|
|
40
40
|
const output = `${label}${message}`;
|
|
@@ -44,6 +44,9 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
44
44
|
else {
|
|
45
45
|
this.log.debug(`[Z-Wave JS] ${output}`);
|
|
46
46
|
}
|
|
47
|
+
if (next) {
|
|
48
|
+
next();
|
|
49
|
+
}
|
|
47
50
|
}
|
|
48
51
|
};
|
|
49
52
|
const logConfig = {
|
|
@@ -62,6 +65,7 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
62
65
|
features: {
|
|
63
66
|
softReset: false,
|
|
64
67
|
},
|
|
68
|
+
emitValueUpdateAfterSetValue: true,
|
|
65
69
|
});
|
|
66
70
|
this.driver.on('error', (err) => {
|
|
67
71
|
this.log.error('Z-Wave driver error:', err);
|
|
@@ -186,6 +190,12 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
186
190
|
if (listeners) {
|
|
187
191
|
node.off('ready', listeners.ready);
|
|
188
192
|
node.off('value updated', listeners.value);
|
|
193
|
+
if (listeners.interviewStageCompleted) {
|
|
194
|
+
node.off('interview stage completed', listeners.interviewStageCompleted);
|
|
195
|
+
}
|
|
196
|
+
if (listeners.interviewFailed) {
|
|
197
|
+
node.off('interview failed', listeners.interviewFailed);
|
|
198
|
+
}
|
|
189
199
|
}
|
|
190
200
|
}
|
|
191
201
|
this.nodeListeners.clear();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
+
import { ValueID, SetValueResult } from 'zwave-js';
|
|
2
3
|
export interface IZWaveController extends EventEmitter {
|
|
3
4
|
homeId: number | undefined;
|
|
4
|
-
nodes: Map<number,
|
|
5
|
+
nodes: Map<number, IZWaveNode>;
|
|
5
6
|
start(): Promise<void>;
|
|
6
7
|
stop(): Promise<void>;
|
|
7
8
|
startInclusion(): Promise<boolean>;
|
|
@@ -16,15 +17,16 @@ export interface IZWaveNode {
|
|
|
16
17
|
name?: string;
|
|
17
18
|
deviceConfig?: {
|
|
18
19
|
label?: string;
|
|
20
|
+
manufacturer?: string;
|
|
19
21
|
};
|
|
20
22
|
ready: boolean;
|
|
21
|
-
interviewStage:
|
|
22
|
-
getValue(valueId:
|
|
23
|
-
setValue(valueId:
|
|
23
|
+
interviewStage: unknown;
|
|
24
|
+
getValue(valueId: ValueID): unknown;
|
|
25
|
+
setValue(valueId: ValueID, value: unknown): Promise<SetValueResult>;
|
|
24
26
|
supportsCC(cc: number): boolean;
|
|
25
|
-
getDefinedValueIDs():
|
|
26
|
-
getAllEndpoints():
|
|
27
|
-
getValueMetadata(valueId:
|
|
28
|
-
on(event: string, listener: (...args:
|
|
29
|
-
off(event: string, listener: (...args:
|
|
27
|
+
getDefinedValueIDs(): ValueID[];
|
|
28
|
+
getAllEndpoints(): unknown[];
|
|
29
|
+
getValueMetadata(valueId: ValueID): unknown;
|
|
30
|
+
on(event: string, listener: (...args: unknown[]) => void): this;
|
|
31
|
+
off(event: string, listener: (...args: unknown[]) => void): this;
|
|
30
32
|
}
|
package/package.json
CHANGED