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.
@@ -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
- securityKeys.S0_Legacy = Buffer.from(this.options.securityKeys.S0_Legacy, 'hex');
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
- securityKeys.S2_Unauthenticated = Buffer.from(this.options.securityKeys.S2_Unauthenticated, 'hex');
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
- securityKeys.S2_Authenticated = Buffer.from(this.options.securityKeys.S2_Authenticated, 'hex');
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
- securityKeys.S2_AccessControl = Buffer.from(this.options.securityKeys.S2_AccessControl, 'hex');
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 configuration - use default console logging to avoid winston compatibility issues
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
- 'inclusion started': (secure) => this.emit('inclusion started', secure), // eslint-disable-line @typescript-eslint/no-explicit-any
66
- 'inclusion stopped': () => this.emit('inclusion stopped'),
67
- 'exclusion started': () => this.emit('exclusion started'),
68
- 'exclusion stopped': () => this.emit('exclusion stopped'),
69
- 'rebuild routes progress': (progress) => this.emit('heal network progress', progress), // eslint-disable-line @typescript-eslint/no-explicit-any
70
- 'rebuild routes done': (result) => this.emit('heal network done', result), // eslint-disable-line @typescript-eslint/no-explicit-any
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} added (Interview Stage: ${node.interviewStage})`);
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-zwave-usb",
3
3
  "displayName": "Z-Wave USB",
4
- "version": "1.2.6",
4
+ "version": "1.2.7",
5
5
  "description": "A Homebridge dynamic platform plugin for Z-Wave USB controllers using zwave-js.",
6
6
  "repository": {
7
7
  "type": "git",