homebridge-zwave-usb 3.6.2 → 3.6.3

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.
@@ -32,6 +32,11 @@ export declare class ZWaveAccessory {
32
32
  }): void;
33
33
  private getDesiredName;
34
34
  private getEffectiveName;
35
+ private formatFingerprintPart;
36
+ private getFingerprint;
37
+ private getModel;
38
+ private getSerialNumber;
39
+ private getCachedDeviceSerialNumber;
35
40
  private applyAccessoryMetadata;
36
41
  private ensureAccessoryInformationConfiguredName;
37
42
  private pruneUnmanagedServices;
@@ -225,10 +225,59 @@ class ZWaveAccessory {
225
225
  getEffectiveName() {
226
226
  return this.platformAccessory.displayName || this.getDesiredName();
227
227
  }
228
+ formatFingerprintPart(value) {
229
+ if (value === undefined) {
230
+ return undefined;
231
+ }
232
+ return `0x${value.toString(16).padStart(4, '0').toUpperCase()}`;
233
+ }
234
+ getFingerprint() {
235
+ const manufacturerId = this.formatFingerprintPart(this.node.manufacturerId);
236
+ const productType = this.formatFingerprintPart(this.node.productType);
237
+ const productId = this.formatFingerprintPart(this.node.productId);
238
+ if (!manufacturerId || !productType || !productId) {
239
+ return undefined;
240
+ }
241
+ return `${manufacturerId}:${productType}:${productId}`;
242
+ }
243
+ getModel() {
244
+ const fingerprint = this.getFingerprint();
245
+ const label = this.node.label || this.node.deviceConfig?.label;
246
+ if (label && fingerprint) {
247
+ return `${label} (${fingerprint})`;
248
+ }
249
+ return fingerprint || label || `Node ${this.node.nodeId}`;
250
+ }
251
+ getSerialNumber() {
252
+ const cachedSerialNumber = this.getCachedDeviceSerialNumber();
253
+ if (cachedSerialNumber) {
254
+ return cachedSerialNumber;
255
+ }
256
+ const fingerprint = this.getFingerprint();
257
+ if (fingerprint) {
258
+ return `zwave-${fingerprint}-node-${this.node.nodeId}`;
259
+ }
260
+ return `node-${this.node.nodeId}`;
261
+ }
262
+ getCachedDeviceSerialNumber() {
263
+ if (this.node.deviceSerialNumber) {
264
+ return this.node.deviceSerialNumber;
265
+ }
266
+ if (typeof this.node.getValue !== 'function') {
267
+ return undefined;
268
+ }
269
+ const cachedValue = this.node.getValue({
270
+ commandClass: core_1.CommandClasses['Manufacturer Specific'],
271
+ endpoint: 0,
272
+ property: 'deviceId',
273
+ propertyKey: 'SerialNumber',
274
+ });
275
+ return typeof cachedValue === 'string' && cachedValue.length > 0 ? cachedValue : undefined;
276
+ }
228
277
  applyAccessoryMetadata(options = {}) {
229
278
  const manufacturer = this.node.manufacturer || this.node.deviceConfig?.manufacturer || 'Unknown';
230
- const model = this.node.label || this.node.deviceConfig?.label || `Node ${this.node.nodeId}`;
231
- const serial = `Node ${this.node.nodeId}`;
279
+ const model = this.getModel();
280
+ const serial = this.getSerialNumber();
232
281
  const name = this.getEffectiveName();
233
282
  const firmwareRevision = this.node.firmwareVersion || '1.0.0';
234
283
  const metadataSignature = JSON.stringify({
@@ -36,6 +36,7 @@ export declare class ZWaveController extends EventEmitter implements IZWaveContr
36
36
  private setupControllerListeners;
37
37
  get homeId(): number | undefined;
38
38
  private addNode;
39
+ private refreshNodeDeviceSerialNumber;
39
40
  private removeNode;
40
41
  /**
41
42
  * Parses security keys from options into Buffer format for Z-Wave JS.
@@ -9,6 +9,7 @@ const core_1 = require("@zwave-js/core");
9
9
  const events_1 = require("events");
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const fs_1 = __importDefault(require("fs"));
12
+ const MANUFACTURER_SPECIFIC_SERIAL_NUMBER_TYPE = 1;
12
13
  /**
13
14
  * ZWaveController wraps the Z-Wave JS Driver and implements high-level
14
15
  * automation logic for inclusion, exclusion, and security PIN management.
@@ -161,6 +162,7 @@ class ZWaveController extends events_1.EventEmitter {
161
162
  this.log.info(`Node ${node.nodeId} is ready (Interview Stage: ${node.interviewStage})`);
162
163
  this.emit('status updated', `Node ${node.nodeId} Ready`);
163
164
  this.emit('node ready', node);
165
+ void this.refreshNodeDeviceSerialNumber(node);
164
166
  };
165
167
  const onValueUpdated = (n, args) => {
166
168
  this.log.debug(`Node ${n.nodeId} value updated`);
@@ -187,6 +189,7 @@ class ZWaveController extends events_1.EventEmitter {
187
189
  this.log.info(`Node ${node.nodeId} has woken up. Interview will resume.`);
188
190
  this.emit('status updated', `Node ${node.nodeId} Awake`);
189
191
  this.emit('node updated', node);
192
+ void this.refreshNodeDeviceSerialNumber(node);
190
193
  };
191
194
  const onSleep = () => {
192
195
  this.log.info(`Node ${node.nodeId} has gone to sleep. Interview is paused.`);
@@ -239,6 +242,35 @@ class ZWaveController extends events_1.EventEmitter {
239
242
  if (node.ready) {
240
243
  this.log.info(`Node ${node.nodeId} is already ready, emitting node ready immediately`);
241
244
  this.emit('node ready', node);
245
+ void this.refreshNodeDeviceSerialNumber(node);
246
+ }
247
+ }
248
+ async refreshNodeDeviceSerialNumber(node) {
249
+ if (node.deviceSerialNumber) {
250
+ return;
251
+ }
252
+ if (typeof node.supportsCC !== 'function' ||
253
+ !node.supportsCC(core_1.CommandClasses['Manufacturer Specific'])) {
254
+ return;
255
+ }
256
+ const manufacturerSpecificApi = node.commandClasses?.['Manufacturer Specific'];
257
+ if (typeof manufacturerSpecificApi?.deviceSpecificGet !== 'function') {
258
+ return;
259
+ }
260
+ try {
261
+ const deviceSerialNumber = await manufacturerSpecificApi.deviceSpecificGet(MANUFACTURER_SPECIFIC_SERIAL_NUMBER_TYPE);
262
+ if (typeof deviceSerialNumber !== 'string' || deviceSerialNumber.length === 0) {
263
+ return;
264
+ }
265
+ if (node.deviceSerialNumber === deviceSerialNumber) {
266
+ return;
267
+ }
268
+ node.deviceSerialNumber = deviceSerialNumber;
269
+ this.log.info(`Node ${node.nodeId} reported device serial number: ${deviceSerialNumber}`);
270
+ this.emit('node updated', node);
271
+ }
272
+ catch (err) {
273
+ this.log.debug(`Node ${node.nodeId} did not provide a device serial number yet: ${err}`);
242
274
  }
243
275
  }
244
276
  removeNode(node) {
@@ -50,6 +50,10 @@ export interface IZWaveNode {
50
50
  name?: string;
51
51
  manufacturer?: string;
52
52
  label?: string;
53
+ deviceSerialNumber?: string;
54
+ manufacturerId?: number;
55
+ productType?: number;
56
+ productId?: number;
53
57
  deviceConfig?: {
54
58
  label?: string;
55
59
  manufacturer?: string;
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": "3.6.2",
4
+ "version": "3.6.3",
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": {