homebridge-zwave-usb 1.2.6 → 1.2.7
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/config.schema.json +22 -1
- package/dist/accessories/ControllerAccessory.js +3 -0
- package/dist/features/BatteryFeature.js +4 -0
- package/dist/features/BinarySwitchFeature.js +5 -1
- package/dist/features/CentralSceneFeature.js +2 -0
- package/dist/features/LockFeature.js +4 -1
- package/dist/features/MultilevelSwitchFeature.js +8 -2
- package/dist/zwave/ZWaveController.js +42 -14
- package/homebridge-ui/public/index.html +23 -1
- package/package.json +1 -1
package/config.schema.json
CHANGED
|
@@ -75,6 +75,25 @@
|
|
|
75
75
|
"type": "boolean",
|
|
76
76
|
"default": false,
|
|
77
77
|
"description": "If true, redirects all Z-Wave JS driver logs to Homebridge."
|
|
78
|
+
},
|
|
79
|
+
"enableServer": {
|
|
80
|
+
"title": "Enable Z-Wave Server (Host Mode)",
|
|
81
|
+
"type": "boolean",
|
|
82
|
+
"default": false,
|
|
83
|
+
"description": "Expose the Z-Wave Driver via WebSocket. Allows Z-Wave JS UI to connect to this plugin."
|
|
84
|
+
},
|
|
85
|
+
"serverPort": {
|
|
86
|
+
"title": "Server Port",
|
|
87
|
+
"type": "integer",
|
|
88
|
+
"default": 3000,
|
|
89
|
+
"minimum": 1024,
|
|
90
|
+
"maximum": 65535,
|
|
91
|
+
"description": "Port for the Z-Wave WebSocket Server (only if enabled).",
|
|
92
|
+
"condition": {
|
|
93
|
+
"function": {
|
|
94
|
+
"enableServer": true
|
|
95
|
+
}
|
|
96
|
+
}
|
|
78
97
|
}
|
|
79
98
|
}
|
|
80
99
|
},
|
|
@@ -102,7 +121,9 @@
|
|
|
102
121
|
"expandable": true,
|
|
103
122
|
"items": [
|
|
104
123
|
"inclusionTimeoutSeconds",
|
|
105
|
-
"debug"
|
|
124
|
+
"debug",
|
|
125
|
+
"enableServer",
|
|
126
|
+
"serverPort"
|
|
106
127
|
]
|
|
107
128
|
}
|
|
108
129
|
]
|
|
@@ -14,6 +14,9 @@ class ControllerAccessory {
|
|
|
14
14
|
this.platform = platform;
|
|
15
15
|
this.controller = controller;
|
|
16
16
|
const homeId = this.controller.homeId;
|
|
17
|
+
if (!homeId) {
|
|
18
|
+
throw new Error('Cannot create ControllerAccessory: homeId is not available');
|
|
19
|
+
}
|
|
17
20
|
const uuid = this.platform.api.hap.uuid.generate(`homebridge-zwave-usb-controller-${homeId}`);
|
|
18
21
|
const existingAccessory = this.platform.accessories.find((accessory) => accessory.UUID === uuid);
|
|
19
22
|
if (existingAccessory) {
|
|
@@ -38,6 +38,10 @@ class BatteryFeature extends ZWaveFeature_1.BaseFeature {
|
|
|
38
38
|
return value ? this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
|
|
39
39
|
}
|
|
40
40
|
const level = this.getBatteryLevel();
|
|
41
|
+
// -1 means unknown/unavailable, don't show low battery warning for unknown
|
|
42
|
+
if (level < 0) {
|
|
43
|
+
return this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
|
|
44
|
+
}
|
|
41
45
|
return level <= 15 ? this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL;
|
|
42
46
|
}
|
|
43
47
|
handleGetBatteryLevel() {
|
|
@@ -30,7 +30,11 @@ class BinarySwitchFeature extends ZWaveFeature_1.BaseFeature {
|
|
|
30
30
|
}
|
|
31
31
|
async handleSetOn(value) {
|
|
32
32
|
try {
|
|
33
|
-
await this.node.setValue({ commandClass: 37, property: 'targetValue', endpoint: this.endpoint.index }, value);
|
|
33
|
+
const result = await this.node.setValue({ commandClass: 37, property: 'targetValue', endpoint: this.endpoint.index }, value);
|
|
34
|
+
// Check if the command was accepted
|
|
35
|
+
if (result.status !== 255) { // 255 = Success
|
|
36
|
+
this.platform.log.warn(`Switch setValue returned status ${result.status} for node ${this.node.nodeId}`);
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
39
|
catch (err) {
|
|
36
40
|
this.platform.log.error(`Failed to set switch value for node ${this.node.nodeId} endpoint ${this.endpoint.index}:`, err);
|
|
@@ -10,6 +10,8 @@ class CentralSceneFeature extends ZWaveFeature_1.BaseFeature {
|
|
|
10
10
|
if (sceneValue) {
|
|
11
11
|
const subType = this.endpoint.index.toString();
|
|
12
12
|
this.service = this.getService(this.platform.Service.StatelessProgrammableSwitch, 'Buttons', subType);
|
|
13
|
+
// Initialize the characteristic binding
|
|
14
|
+
this.service.getCharacteristic(this.platform.Characteristic.ProgrammableSwitchEvent);
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
update() {
|
|
@@ -64,7 +64,10 @@ class LockFeature extends ZWaveFeature_1.BaseFeature {
|
|
|
64
64
|
async handleSetLockTargetState(value) {
|
|
65
65
|
const targetMode = value === this.platform.Characteristic.LockTargetState.SECURED ? 255 : 0;
|
|
66
66
|
try {
|
|
67
|
-
await this.node.setValue({ commandClass: 98, property: 'targetMode', endpoint: this.endpoint.index }, targetMode);
|
|
67
|
+
const result = await this.node.setValue({ commandClass: 98, property: 'targetMode', endpoint: this.endpoint.index }, targetMode);
|
|
68
|
+
if (result.status !== 255) {
|
|
69
|
+
this.platform.log.warn(`Lock setValue returned status ${result.status} for node ${this.node.nodeId}`);
|
|
70
|
+
}
|
|
68
71
|
}
|
|
69
72
|
catch (err) {
|
|
70
73
|
this.platform.log.error(`Failed to set Lock Target for node ${this.node.nodeId} endpoint ${this.endpoint.index}:`, err);
|
|
@@ -49,7 +49,10 @@ class MultilevelSwitchFeature extends ZWaveFeature_1.BaseFeature {
|
|
|
49
49
|
async handleSetOn(value) {
|
|
50
50
|
const targetValue = value ? 255 : 0;
|
|
51
51
|
try {
|
|
52
|
-
await this.node.setValue({ commandClass: 38, property: 'targetValue', endpoint: this.endpoint.index }, targetValue);
|
|
52
|
+
const result = await this.node.setValue({ commandClass: 38, property: 'targetValue', endpoint: this.endpoint.index }, targetValue);
|
|
53
|
+
if (result.status !== 255) {
|
|
54
|
+
this.platform.log.warn(`Multilevel switch setValue returned status ${result.status} for node ${this.node.nodeId}`);
|
|
55
|
+
}
|
|
53
56
|
}
|
|
54
57
|
catch (err) {
|
|
55
58
|
this.platform.log.error(`Failed to set ON/OFF for node ${this.node.nodeId} endpoint ${this.endpoint.index}:`, err);
|
|
@@ -69,7 +72,10 @@ class MultilevelSwitchFeature extends ZWaveFeature_1.BaseFeature {
|
|
|
69
72
|
async handleSetBrightness(value) {
|
|
70
73
|
const targetValue = Math.min(Math.max(value, 0), 99);
|
|
71
74
|
try {
|
|
72
|
-
await this.node.setValue({ commandClass: 38, property: 'targetValue', endpoint: this.endpoint.index }, targetValue);
|
|
75
|
+
const result = await this.node.setValue({ commandClass: 38, property: 'targetValue', endpoint: this.endpoint.index }, targetValue);
|
|
76
|
+
if (result.status !== 255) {
|
|
77
|
+
this.platform.log.warn(`Brightness setValue returned status ${result.status} for node ${this.node.nodeId}`);
|
|
78
|
+
}
|
|
73
79
|
}
|
|
74
80
|
catch (err) {
|
|
75
81
|
this.platform.log.error(`Failed to set Brightness for node ${this.node.nodeId} endpoint ${this.endpoint.index}:`, err);
|
|
@@ -20,20 +20,27 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
20
20
|
const securityKeys = {};
|
|
21
21
|
if (this.options.securityKeys) {
|
|
22
22
|
if (this.options.securityKeys.S0_Legacy) {
|
|
23
|
-
|
|
23
|
+
if (this.options.securityKeys.S0_Legacy.length === 32) {
|
|
24
|
+
securityKeys.S0_Legacy = Buffer.from(this.options.securityKeys.S0_Legacy, 'hex');
|
|
25
|
+
}
|
|
24
26
|
}
|
|
25
27
|
if (this.options.securityKeys.S2_Unauthenticated) {
|
|
26
|
-
|
|
28
|
+
if (this.options.securityKeys.S2_Unauthenticated.length === 32) {
|
|
29
|
+
securityKeys.S2_Unauthenticated = Buffer.from(this.options.securityKeys.S2_Unauthenticated, 'hex');
|
|
30
|
+
}
|
|
27
31
|
}
|
|
28
32
|
if (this.options.securityKeys.S2_Authenticated) {
|
|
29
|
-
|
|
33
|
+
if (this.options.securityKeys.S2_Authenticated.length === 32) {
|
|
34
|
+
securityKeys.S2_Authenticated = Buffer.from(this.options.securityKeys.S2_Authenticated, 'hex');
|
|
35
|
+
}
|
|
30
36
|
}
|
|
31
37
|
if (this.options.securityKeys.S2_AccessControl) {
|
|
32
|
-
|
|
38
|
+
if (this.options.securityKeys.S2_AccessControl.length === 32) {
|
|
39
|
+
securityKeys.S2_AccessControl = Buffer.from(this.options.securityKeys.S2_AccessControl, 'hex');
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
|
-
// Z-Wave JS logging
|
|
36
|
-
// Custom logging can be added in future if needed
|
|
43
|
+
// Z-Wave JS logging - use simple config to avoid type issues
|
|
37
44
|
const logConfig = {
|
|
38
45
|
enabled: true,
|
|
39
46
|
level: this.options.debug ? 'debug' : 'info',
|
|
@@ -41,7 +48,7 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
41
48
|
const storagePath = this.options.storagePath || process.cwd();
|
|
42
49
|
this.driver = new zwave_js_1.Driver(this.serialPort, {
|
|
43
50
|
securityKeys: Object.keys(securityKeys).length > 0 ? securityKeys : undefined,
|
|
44
|
-
logConfig,
|
|
51
|
+
logConfig: logConfig,
|
|
45
52
|
storage: {
|
|
46
53
|
cacheDir: storagePath + '/zwave-js-cache',
|
|
47
54
|
deviceConfigPriorityDir: storagePath + '/zwave-js-config',
|
|
@@ -62,17 +69,34 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
62
69
|
try {
|
|
63
70
|
this.log.info(`Controller Home ID: ${this.driver.controller.homeId}`);
|
|
64
71
|
this.controllerListeners = {
|
|
65
|
-
|
|
66
|
-
'inclusion
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
'
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
|
+
'inclusion started': (secure) => {
|
|
74
|
+
this.log.info(`Inclusion started (secure: ${secure})`);
|
|
75
|
+
this.emit('inclusion started', secure);
|
|
76
|
+
},
|
|
77
|
+
'inclusion stopped': () => {
|
|
78
|
+
this.log.info('Inclusion stopped');
|
|
79
|
+
this.emit('inclusion stopped');
|
|
80
|
+
},
|
|
81
|
+
'exclusion started': () => {
|
|
82
|
+
this.log.info('Exclusion started');
|
|
83
|
+
this.emit('exclusion started');
|
|
84
|
+
},
|
|
85
|
+
'exclusion stopped': () => {
|
|
86
|
+
this.log.info('Exclusion stopped');
|
|
87
|
+
this.emit('exclusion stopped');
|
|
88
|
+
},
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
+
'rebuild routes progress': (progress) => this.emit('heal network progress', progress),
|
|
91
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
92
|
+
'rebuild routes done': (result) => this.emit('heal network done', result),
|
|
71
93
|
'node added': (node) => {
|
|
94
|
+
this.log.info(`Node ${node.nodeId} added to controller`);
|
|
72
95
|
this.addNode(node);
|
|
73
96
|
this.emit('node added', node);
|
|
74
97
|
},
|
|
75
98
|
'node removed': (node) => {
|
|
99
|
+
this.log.info(`Node ${node.nodeId} removed from controller`);
|
|
76
100
|
this.removeNode(node);
|
|
77
101
|
},
|
|
78
102
|
};
|
|
@@ -105,14 +129,17 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
105
129
|
}
|
|
106
130
|
addNode(node) {
|
|
107
131
|
if (this.nodes.has(node.nodeId)) {
|
|
132
|
+
this.log.warn(`Node ${node.nodeId} already exists in nodes map, skipping`);
|
|
108
133
|
return;
|
|
109
134
|
}
|
|
110
135
|
this.nodes.set(node.nodeId, node);
|
|
136
|
+
this.log.info(`Node ${node.nodeId} added to controller (Interview Stage: ${node.interviewStage})`);
|
|
111
137
|
const onReady = () => {
|
|
112
138
|
this.log.info(`Node ${node.nodeId} is ready (Interview Stage: ${node.interviewStage})`);
|
|
113
139
|
this.emit('node ready', node);
|
|
114
140
|
};
|
|
115
141
|
const onValueUpdated = (_args) => {
|
|
142
|
+
this.log.debug(`Node ${node.nodeId} value updated`);
|
|
116
143
|
this.emit('value updated', node);
|
|
117
144
|
};
|
|
118
145
|
const onInterviewStageCompleted = (stage) => {
|
|
@@ -131,8 +158,9 @@ class ZWaveController extends events_1.EventEmitter {
|
|
|
131
158
|
node.on('value updated', onValueUpdated);
|
|
132
159
|
node.on('interview stage completed', onInterviewStageCompleted);
|
|
133
160
|
node.on('interview failed', onInterviewFailed);
|
|
134
|
-
this.log.info(`Node ${node.nodeId}
|
|
161
|
+
this.log.info(`Node ${node.nodeId} registered with event listeners (ready: ${node.ready})`);
|
|
135
162
|
if (node.ready) {
|
|
163
|
+
this.log.info(`Node ${node.nodeId} is already ready, emitting node ready immediately`);
|
|
136
164
|
this.emit('node ready', node);
|
|
137
165
|
}
|
|
138
166
|
}
|
|
@@ -50,6 +50,16 @@
|
|
|
50
50
|
<input type="checkbox" class="form-check-input" id="debug">
|
|
51
51
|
<label class="form-check-label" for="debug">Enable Verbose Driver Logging</label>
|
|
52
52
|
</div>
|
|
53
|
+
|
|
54
|
+
<div class="form-check mt-3">
|
|
55
|
+
<input type="checkbox" class="form-check-input" id="enableServer">
|
|
56
|
+
<label class="form-check-label" for="enableServer">Enable Z-Wave Server (Debugging)</label>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="form-group mt-2" id="serverPortGroup" style="display:none;">
|
|
60
|
+
<label for="serverPort">Server Port</label>
|
|
61
|
+
<input type="number" class="form-control" id="serverPort" placeholder="3000">
|
|
62
|
+
</div>
|
|
53
63
|
</fieldset>
|
|
54
64
|
</form>
|
|
55
65
|
</div>
|
|
@@ -76,7 +86,9 @@
|
|
|
76
86
|
platform: 'ZWaveUSB',
|
|
77
87
|
serialPort: document.getElementById('serialPort').value,
|
|
78
88
|
inclusionTimeoutSeconds: getInt('inclusionTimeoutSeconds', 60),
|
|
79
|
-
debug: document.getElementById('debug').checked
|
|
89
|
+
debug: document.getElementById('debug').checked,
|
|
90
|
+
enableServer: document.getElementById('enableServer').checked,
|
|
91
|
+
serverPort: getInt('serverPort', 3000)
|
|
80
92
|
};
|
|
81
93
|
|
|
82
94
|
if (Object.values(securityKeys).some(k => k && k.length > 0)) {
|
|
@@ -114,6 +126,16 @@
|
|
|
114
126
|
// Advanced
|
|
115
127
|
document.getElementById('inclusionTimeoutSeconds').value = config.inclusionTimeoutSeconds || 60;
|
|
116
128
|
document.getElementById('debug').checked = config.debug || false;
|
|
129
|
+
document.getElementById('enableServer').checked = config.enableServer || false;
|
|
130
|
+
document.getElementById('serverPort').value = config.serverPort || 3000;
|
|
131
|
+
|
|
132
|
+
// UI Logic
|
|
133
|
+
function toggleServerPort() {
|
|
134
|
+
const enabled = document.getElementById('enableServer').checked;
|
|
135
|
+
document.getElementById('serverPortGroup').style.display = enabled ? 'block' : 'none';
|
|
136
|
+
}
|
|
137
|
+
document.getElementById('enableServer').addEventListener('change', toggleServerPort);
|
|
138
|
+
toggleServerPort();
|
|
117
139
|
|
|
118
140
|
// Auto-save on change
|
|
119
141
|
document.getElementById('configForm').addEventListener('input', updateConfig);
|
package/package.json
CHANGED