homebridge-zwave-usb 1.2.5 → 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,44 +20,35 @@ 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');
33
- }
34
- }
35
- // Redirect Z-Wave JS logs to Homebridge
36
- const customLogTransport = {
37
- log: (info, next) => {
38
- const message = info.message || info;
39
- const label = info.label ? `[${info.label}] ` : '';
40
- const output = `${label}${message}`;
41
- if (this.options.debug || info.level === 'error' || info.level === 'warn') {
42
- this.log.info(`[Z-Wave JS] ${output}`);
43
- }
44
- else {
45
- this.log.debug(`[Z-Wave JS] ${output}`);
46
- }
47
- if (next) {
48
- next();
38
+ if (this.options.securityKeys.S2_AccessControl.length === 32) {
39
+ securityKeys.S2_AccessControl = Buffer.from(this.options.securityKeys.S2_AccessControl, 'hex');
49
40
  }
50
41
  }
51
- };
42
+ }
43
+ // Z-Wave JS logging - use simple config to avoid type issues
52
44
  const logConfig = {
53
45
  enabled: true,
54
46
  level: this.options.debug ? 'debug' : 'info',
55
- transports: [customLogTransport] // eslint-disable-line @typescript-eslint/no-explicit-any
56
47
  };
57
48
  const storagePath = this.options.storagePath || process.cwd();
58
49
  this.driver = new zwave_js_1.Driver(this.serialPort, {
59
50
  securityKeys: Object.keys(securityKeys).length > 0 ? securityKeys : undefined,
60
- logConfig,
51
+ logConfig: logConfig,
61
52
  storage: {
62
53
  cacheDir: storagePath + '/zwave-js-cache',
63
54
  deviceConfigPriorityDir: storagePath + '/zwave-js-config',
@@ -78,17 +69,34 @@ class ZWaveController extends events_1.EventEmitter {
78
69
  try {
79
70
  this.log.info(`Controller Home ID: ${this.driver.controller.homeId}`);
80
71
  this.controllerListeners = {
81
- 'inclusion started': (secure) => this.emit('inclusion started', secure), // eslint-disable-line @typescript-eslint/no-explicit-any
82
- 'inclusion stopped': () => this.emit('inclusion stopped'),
83
- 'exclusion started': () => this.emit('exclusion started'),
84
- 'exclusion stopped': () => this.emit('exclusion stopped'),
85
- 'rebuild routes progress': (progress) => this.emit('heal network progress', progress), // eslint-disable-line @typescript-eslint/no-explicit-any
86
- '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),
87
93
  'node added': (node) => {
94
+ this.log.info(`Node ${node.nodeId} added to controller`);
88
95
  this.addNode(node);
89
96
  this.emit('node added', node);
90
97
  },
91
98
  'node removed': (node) => {
99
+ this.log.info(`Node ${node.nodeId} removed from controller`);
92
100
  this.removeNode(node);
93
101
  },
94
102
  };
@@ -121,14 +129,17 @@ class ZWaveController extends events_1.EventEmitter {
121
129
  }
122
130
  addNode(node) {
123
131
  if (this.nodes.has(node.nodeId)) {
132
+ this.log.warn(`Node ${node.nodeId} already exists in nodes map, skipping`);
124
133
  return;
125
134
  }
126
135
  this.nodes.set(node.nodeId, node);
136
+ this.log.info(`Node ${node.nodeId} added to controller (Interview Stage: ${node.interviewStage})`);
127
137
  const onReady = () => {
128
138
  this.log.info(`Node ${node.nodeId} is ready (Interview Stage: ${node.interviewStage})`);
129
139
  this.emit('node ready', node);
130
140
  };
131
141
  const onValueUpdated = (_args) => {
142
+ this.log.debug(`Node ${node.nodeId} value updated`);
132
143
  this.emit('value updated', node);
133
144
  };
134
145
  const onInterviewStageCompleted = (stage) => {
@@ -147,8 +158,9 @@ class ZWaveController extends events_1.EventEmitter {
147
158
  node.on('value updated', onValueUpdated);
148
159
  node.on('interview stage completed', onInterviewStageCompleted);
149
160
  node.on('interview failed', onInterviewFailed);
150
- 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})`);
151
162
  if (node.ready) {
163
+ this.log.info(`Node ${node.nodeId} is already ready, emitting node ready immediately`);
152
164
  this.emit('node ready', node);
153
165
  }
154
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.5",
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",