homebridge-zwave-usb 3.3.4 → 3.4.0
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/README.md +1 -0
- package/dist/accessories/AccessoryFactory.d.ts +3 -1
- package/dist/accessories/AccessoryFactory.js +2 -2
- package/dist/accessories/ControllerAccessory.d.ts +0 -1
- package/dist/accessories/ControllerAccessory.js +0 -14
- package/dist/accessories/ZWaveAccessory.d.ts +7 -3
- package/dist/accessories/ZWaveAccessory.js +46 -33
- package/dist/features/ZWaveFeature.js +16 -13
- package/dist/platform/ZWaveUsbPlatform.d.ts +1 -0
- package/dist/platform/ZWaveUsbPlatform.js +26 -6
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/homebridge-zwave-usb)
|
|
5
5
|
[](https://github.com/sapireli/homebridge-zwave-usb/actions/workflows/build.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
|
+
[](https://www.paypal.me/sapir)
|
|
7
8
|
|
|
8
9
|
A high-performance, production-grade [Homebridge](https://homebridge.io) integration for Z-Wave networks. Built directly on the modern [Z-Wave JS](https://zwave-js.github.io/node-zwave-js/) driver, this plugin provides direct communication with USB controllers for ultra-low latency and maximum reliability.
|
|
9
10
|
|
|
@@ -2,6 +2,8 @@ import { IZWaveNode } from '../zwave/interfaces';
|
|
|
2
2
|
import { ZWaveAccessory } from './ZWaveAccessory';
|
|
3
3
|
import { ZWaveUsbPlatform } from '../platform/ZWaveUsbPlatform';
|
|
4
4
|
export declare class AccessoryFactory {
|
|
5
|
-
static create(platform: ZWaveUsbPlatform, node: IZWaveNode, homeId: number
|
|
5
|
+
static create(platform: ZWaveUsbPlatform, node: IZWaveNode, homeId: number, options?: {
|
|
6
|
+
forceUuidSeed?: string;
|
|
7
|
+
}): ZWaveAccessory;
|
|
6
8
|
private static attachFeatures;
|
|
7
9
|
}
|
|
@@ -20,8 +20,8 @@ const GarageDoorFeature_1 = require("../features/GarageDoorFeature");
|
|
|
20
20
|
const ColorSwitchFeature_1 = require("../features/ColorSwitchFeature");
|
|
21
21
|
const SirenFeature_1 = require("../features/SirenFeature");
|
|
22
22
|
class AccessoryFactory {
|
|
23
|
-
static create(platform, node, homeId) {
|
|
24
|
-
const accessory = new ZWaveAccessory_1.ZWaveAccessory(platform, node, homeId);
|
|
23
|
+
static create(platform, node, homeId, options = {}) {
|
|
24
|
+
const accessory = new ZWaveAccessory_1.ZWaveAccessory(platform, node, homeId, options);
|
|
25
25
|
const endpoints = node.getAllEndpoints();
|
|
26
26
|
const isMultiEndpoint = endpoints.length > 1;
|
|
27
27
|
// Track which CCs are handled by non-zero endpoints to avoid duplicates on root
|
|
@@ -21,7 +21,6 @@ export declare class ControllerAccessory {
|
|
|
21
21
|
private handlers;
|
|
22
22
|
constructor(platform: ZWaveUsbPlatform, controller: IZWaveController);
|
|
23
23
|
private setupControllerHandlers;
|
|
24
|
-
private syncConfiguredName;
|
|
25
24
|
private setupPinEntryCharacteristic;
|
|
26
25
|
private handleSetInclusion;
|
|
27
26
|
private handleSetExclusion;
|
|
@@ -111,7 +111,6 @@ class ControllerAccessory {
|
|
|
111
111
|
this.statusService =
|
|
112
112
|
this.platformAccessory.getService(settings_1.MANAGER_SERVICE_UUID) ||
|
|
113
113
|
this.platformAccessory.addService(new managerServiceType('System Status', 'Status'));
|
|
114
|
-
this.syncConfiguredName(this.statusService, 'System Status');
|
|
115
114
|
this.statusService
|
|
116
115
|
.getCharacteristic(this.platform.Characteristic.Name)
|
|
117
116
|
.setProps({
|
|
@@ -152,7 +151,6 @@ class ControllerAccessory {
|
|
|
152
151
|
this.inclusionService =
|
|
153
152
|
this.platformAccessory.getServiceById(this.platform.Service.Switch, 'Inclusion') ||
|
|
154
153
|
this.platformAccessory.addService(this.platform.Service.Switch, 'Inclusion Mode', 'Inclusion');
|
|
155
|
-
this.syncConfiguredName(this.inclusionService, 'Inclusion Mode');
|
|
156
154
|
this.inclusionService
|
|
157
155
|
.getCharacteristic(this.platform.Characteristic.Name)
|
|
158
156
|
.setProps({
|
|
@@ -173,7 +171,6 @@ class ControllerAccessory {
|
|
|
173
171
|
this.exclusionService =
|
|
174
172
|
this.platformAccessory.getServiceById(this.platform.Service.Switch, 'Exclusion') ||
|
|
175
173
|
this.platformAccessory.addService(this.platform.Service.Switch, 'Exclusion Mode', 'Exclusion');
|
|
176
|
-
this.syncConfiguredName(this.exclusionService, 'Exclusion Mode');
|
|
177
174
|
this.exclusionService
|
|
178
175
|
.getCharacteristic(this.platform.Characteristic.Name)
|
|
179
176
|
.setProps({
|
|
@@ -193,7 +190,6 @@ class ControllerAccessory {
|
|
|
193
190
|
this.healService =
|
|
194
191
|
this.platformAccessory.getServiceById(this.platform.Service.Switch, 'Heal') ||
|
|
195
192
|
this.platformAccessory.addService(this.platform.Service.Switch, 'Heal Network', 'Heal');
|
|
196
|
-
this.syncConfiguredName(this.healService, 'Heal Network');
|
|
197
193
|
this.healService
|
|
198
194
|
.getCharacteristic(this.platform.Characteristic.Name)
|
|
199
195
|
.setProps({
|
|
@@ -213,7 +209,6 @@ class ControllerAccessory {
|
|
|
213
209
|
this.pruneService =
|
|
214
210
|
this.platformAccessory.getServiceById(this.platform.Service.Switch, 'Prune') ||
|
|
215
211
|
this.platformAccessory.addService(this.platform.Service.Switch, 'Prune Dead Nodes', 'Prune');
|
|
216
|
-
this.syncConfiguredName(this.pruneService, 'Prune Dead Nodes');
|
|
217
212
|
this.pruneService
|
|
218
213
|
.getCharacteristic(this.platform.Characteristic.Name)
|
|
219
214
|
.setProps({
|
|
@@ -325,15 +320,6 @@ class ControllerAccessory {
|
|
|
325
320
|
this.controller.on(event, handler);
|
|
326
321
|
}
|
|
327
322
|
}
|
|
328
|
-
syncConfiguredName(service, value) {
|
|
329
|
-
const configuredNameValue = value || service.displayName;
|
|
330
|
-
if (!service.testCharacteristic(this.platform.Characteristic.ConfiguredName)) {
|
|
331
|
-
service.addOptionalCharacteristic(this.platform.Characteristic.ConfiguredName);
|
|
332
|
-
}
|
|
333
|
-
service
|
|
334
|
-
.getCharacteristic(this.platform.Characteristic.ConfiguredName)
|
|
335
|
-
.updateValue(configuredNameValue);
|
|
336
|
-
}
|
|
337
323
|
setupPinEntryCharacteristic(service) {
|
|
338
324
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
339
325
|
const pinCharType = this.platform.Characteristic.S2PinEntry;
|
|
@@ -6,13 +6,17 @@ export declare class ZWaveAccessory {
|
|
|
6
6
|
readonly platform: ZWaveUsbPlatform;
|
|
7
7
|
node: IZWaveNode;
|
|
8
8
|
readonly homeId: number;
|
|
9
|
+
private readonly options;
|
|
9
10
|
readonly platformAccessory: PlatformAccessory;
|
|
10
11
|
private features;
|
|
11
12
|
private initialized;
|
|
12
|
-
constructor(platform: ZWaveUsbPlatform, node: IZWaveNode, homeId: number
|
|
13
|
+
constructor(platform: ZWaveUsbPlatform, node: IZWaveNode, homeId: number, options?: {
|
|
14
|
+
forceUuidSeed?: string;
|
|
15
|
+
});
|
|
13
16
|
addFeature(feature: ZWaveFeature): void;
|
|
14
17
|
/**
|
|
15
|
-
*
|
|
18
|
+
* Applies a plugin-controlled rename to the default accessory metadata.
|
|
19
|
+
* This is intentionally conservative and is only used for explicit recreate flows.
|
|
16
20
|
*/
|
|
17
21
|
rename(newName: string): void;
|
|
18
22
|
/**
|
|
@@ -23,7 +27,7 @@ export declare class ZWaveAccessory {
|
|
|
23
27
|
*/
|
|
24
28
|
updateNode(newNode: IZWaveNode): void;
|
|
25
29
|
initialize(): void;
|
|
26
|
-
private
|
|
30
|
+
private seedConfiguredNameOnPrimaryService;
|
|
27
31
|
refresh(args?: ZWaveValueEvent): void;
|
|
28
32
|
stop(): void;
|
|
29
33
|
}
|
|
@@ -7,17 +7,20 @@ class ZWaveAccessory {
|
|
|
7
7
|
platform;
|
|
8
8
|
node;
|
|
9
9
|
homeId;
|
|
10
|
+
options;
|
|
10
11
|
platformAccessory;
|
|
11
12
|
features = [];
|
|
12
13
|
initialized = false;
|
|
13
|
-
constructor(platform, node, homeId) {
|
|
14
|
+
constructor(platform, node, homeId, options = {}) {
|
|
14
15
|
this.platform = platform;
|
|
15
16
|
this.node = node;
|
|
16
17
|
this.homeId = homeId;
|
|
18
|
+
this.options = options;
|
|
17
19
|
// WARNING: This UUID generation string MUST NOT BE CHANGED!
|
|
18
20
|
// This deterministic string ensures that devices maintain the same HomeKit identity across restarts.
|
|
19
21
|
// This is the stable UUID generation scheme.
|
|
20
|
-
const
|
|
22
|
+
const stableId = `homebridge-zwave-usb-${this.homeId}-${this.node.nodeId}`;
|
|
23
|
+
const stableUuid = this.platform.api.hap.uuid.generate(stableId);
|
|
21
24
|
/**
|
|
22
25
|
* MIGRATION PATH: Automated Legacy UUID Adoption.
|
|
23
26
|
* To prevent user automations from breaking after a plugin update that changed UUID schemes,
|
|
@@ -39,14 +42,22 @@ class ZWaveAccessory {
|
|
|
39
42
|
break;
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
|
-
const existingAccessory = this.platform.accessories.find((accessory) =>
|
|
45
|
+
const existingAccessory = this.platform.accessories.find((accessory) => {
|
|
46
|
+
const context = accessory.context;
|
|
47
|
+
return context?.nodeId === this.node.nodeId && context?.homeId === this.homeId;
|
|
48
|
+
}) ||
|
|
49
|
+
this.platform.accessories.find((accessory) => accessory.UUID === uuid);
|
|
43
50
|
const nodeName = this.node.name || this.node.deviceConfig?.label || `Node ${this.node.nodeId}`;
|
|
51
|
+
const forceUuidSeed = this.options.forceUuidSeed?.trim();
|
|
44
52
|
if (existingAccessory) {
|
|
45
53
|
this.platformAccessory = existingAccessory;
|
|
46
54
|
}
|
|
47
55
|
else {
|
|
56
|
+
const creationUuid = forceUuidSeed
|
|
57
|
+
? this.platform.api.hap.uuid.generate(`${stableId}-rename-${forceUuidSeed}`)
|
|
58
|
+
: uuid;
|
|
48
59
|
this.platform.log.info(`Creating new accessory for ${nodeName} (UUID: ${uuid})`);
|
|
49
|
-
this.platformAccessory = new this.platform.api.platformAccessory(nodeName,
|
|
60
|
+
this.platformAccessory = new this.platform.api.platformAccessory(nodeName, creationUuid);
|
|
50
61
|
this.platform.api.registerPlatformAccessories('homebridge-zwave-usb', 'ZWaveUSB', [
|
|
51
62
|
this.platformAccessory,
|
|
52
63
|
]);
|
|
@@ -62,6 +73,18 @@ class ZWaveAccessory {
|
|
|
62
73
|
.setCharacteristic(this.platform.Characteristic.Model, model)
|
|
63
74
|
.setCharacteristic(this.platform.Characteristic.SerialNumber, serial)
|
|
64
75
|
.setCharacteristic(this.platform.Characteristic.FirmwareRevision, this.node.firmwareVersion || '1.0.0');
|
|
76
|
+
if (!existingAccessory) {
|
|
77
|
+
// Seed the initial HomeKit-facing name once for freshly created accessories.
|
|
78
|
+
this.platformAccessory.displayName = nodeName;
|
|
79
|
+
infoService.setCharacteristic(this.platform.Characteristic.Name, nodeName);
|
|
80
|
+
}
|
|
81
|
+
const context = (this.platformAccessory.context || {});
|
|
82
|
+
this.platformAccessory.context = context;
|
|
83
|
+
context.nodeId = this.node.nodeId;
|
|
84
|
+
context.homeId = this.homeId;
|
|
85
|
+
if (forceUuidSeed) {
|
|
86
|
+
context.renameGeneration = forceUuidSeed;
|
|
87
|
+
}
|
|
65
88
|
/**
|
|
66
89
|
* Helper to normalize UUIDs for reliable comparison during metadata pruning.
|
|
67
90
|
*/
|
|
@@ -86,22 +109,19 @@ class ZWaveAccessory {
|
|
|
86
109
|
this.features.push(feature);
|
|
87
110
|
}
|
|
88
111
|
/**
|
|
89
|
-
*
|
|
112
|
+
* Applies a plugin-controlled rename to the default accessory metadata.
|
|
113
|
+
* This is intentionally conservative and is only used for explicit recreate flows.
|
|
90
114
|
*/
|
|
91
115
|
rename(newName) {
|
|
92
116
|
this.platform.log.info(`Syncing HomeKit name for Node ${this.node.nodeId} -> ${newName}`);
|
|
93
117
|
this.platformAccessory.displayName = newName;
|
|
94
118
|
const infoService = this.platformAccessory.getService(this.platform.Service.AccessoryInformation);
|
|
95
119
|
if (infoService) {
|
|
96
|
-
|
|
97
|
-
if (configuredNameChar) {
|
|
98
|
-
configuredNameChar.updateValue(newName);
|
|
99
|
-
}
|
|
120
|
+
infoService.setCharacteristic(this.platform.Characteristic.Name, newName);
|
|
100
121
|
}
|
|
101
122
|
for (const feature of this.features) {
|
|
102
123
|
feature.rename(newName);
|
|
103
124
|
}
|
|
104
|
-
this.platform.api.updatePlatformAccessories([this.platformAccessory]);
|
|
105
125
|
}
|
|
106
126
|
/**
|
|
107
127
|
* HOT-RECOVERY FIX: Update stale node references.
|
|
@@ -149,32 +169,25 @@ class ZWaveAccessory {
|
|
|
149
169
|
this.platformAccessory.removeService(service);
|
|
150
170
|
}
|
|
151
171
|
});
|
|
152
|
-
|
|
153
|
-
const infoService = this.platformAccessory.getService(this.platform.Service.AccessoryInformation);
|
|
154
|
-
if (infoService) {
|
|
155
|
-
this.setupConfiguredName(infoService);
|
|
156
|
-
}
|
|
172
|
+
this.seedConfiguredNameOnPrimaryService();
|
|
157
173
|
this.platform.api.updatePlatformAccessories([this.platformAccessory]);
|
|
158
174
|
this.refresh();
|
|
159
175
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this.platform.log.info(`[ConfiguredName:onSet] Service "${service.displayName}" Node ${this.node.nodeId}: ${this.platformAccessory.displayName} -> ${newName}`);
|
|
176
|
-
this.rename(newName);
|
|
177
|
-
});
|
|
176
|
+
seedConfiguredNameOnPrimaryService() {
|
|
177
|
+
const primaryService = this.features.flatMap((feature) => feature.getServices())[0];
|
|
178
|
+
if (!primaryService) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (typeof primaryService.setPrimaryService === 'function') {
|
|
182
|
+
primaryService.setPrimaryService(true);
|
|
183
|
+
}
|
|
184
|
+
if (!primaryService.testCharacteristic(this.platform.Characteristic.ConfiguredName)) {
|
|
185
|
+
primaryService.addOptionalCharacteristic(this.platform.Characteristic.ConfiguredName);
|
|
186
|
+
}
|
|
187
|
+
const configuredName = primaryService.getCharacteristic(this.platform.Characteristic.ConfiguredName);
|
|
188
|
+
if (!configuredName.value) {
|
|
189
|
+
configuredName.updateValue(primaryService.displayName || this.platformAccessory.displayName);
|
|
190
|
+
}
|
|
178
191
|
}
|
|
179
192
|
refresh(args) {
|
|
180
193
|
/**
|
|
@@ -68,28 +68,31 @@ class BaseFeature {
|
|
|
68
68
|
? `${this.accessory.displayName} ${this.endpoint.index}`
|
|
69
69
|
: this.accessory.displayName);
|
|
70
70
|
let service;
|
|
71
|
+
let wasCreated = false;
|
|
71
72
|
if (subType) {
|
|
72
73
|
service =
|
|
73
|
-
this.accessory.getServiceById(serviceType, subType)
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
this.accessory.getServiceById(serviceType, subType);
|
|
75
|
+
if (!service) {
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
service = this.accessory.addService(new serviceType(serviceName, subType));
|
|
78
|
+
wasCreated = true;
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
else {
|
|
78
82
|
service =
|
|
79
|
-
this.accessory.getService(serviceType)
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
this.accessory.getService(serviceType);
|
|
84
|
+
if (!service) {
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
service = this.accessory.addService(new serviceType(serviceName));
|
|
87
|
+
wasCreated = true;
|
|
88
|
+
}
|
|
82
89
|
}
|
|
83
90
|
// Sync internal property
|
|
84
91
|
service.displayName = serviceName;
|
|
85
|
-
//
|
|
86
|
-
if (service.testCharacteristic(this.platform.Characteristic.Name)) {
|
|
92
|
+
// Seed the service Name only when the service is first created.
|
|
93
|
+
if (wasCreated && service.testCharacteristic(this.platform.Characteristic.Name)) {
|
|
87
94
|
const nameChar = service.getCharacteristic(this.platform.Characteristic.Name);
|
|
88
|
-
|
|
89
|
-
const currentValue = nameChar.value;
|
|
90
|
-
if (!currentValue || currentValue.startsWith('Node ')) {
|
|
91
|
-
nameChar.updateValue(serviceName);
|
|
92
|
-
}
|
|
95
|
+
nameChar.updateValue(serviceName);
|
|
93
96
|
}
|
|
94
97
|
// Add Service Label Index for multi-endpoint devices to help with ordering/naming
|
|
95
98
|
if (this.endpoint.index > 0) {
|
|
@@ -27,6 +27,7 @@ export declare class ZWaveUsbPlatform implements DynamicPlatformPlugin {
|
|
|
27
27
|
private handleNodeAdded;
|
|
28
28
|
private handleNodeReady;
|
|
29
29
|
private handleNodeRemoved;
|
|
30
|
+
private recreateNodeAccessory;
|
|
30
31
|
/**
|
|
31
32
|
* Event-Driven Updates:
|
|
32
33
|
* Instead of refreshing every accessory on every change, we route the specific
|
|
@@ -130,7 +130,7 @@ class ZWaveUsbPlatform {
|
|
|
130
130
|
method === 'POST') {
|
|
131
131
|
let body = '';
|
|
132
132
|
req.on('data', (chunk) => (body += chunk));
|
|
133
|
-
req.on('end', () => {
|
|
133
|
+
req.on('end', async () => {
|
|
134
134
|
try {
|
|
135
135
|
const parts = normalizedUrl.split('/');
|
|
136
136
|
const nodeId = parseInt(parts[2], 10);
|
|
@@ -138,11 +138,10 @@ class ZWaveUsbPlatform {
|
|
|
138
138
|
if (!this.zwaveController) {
|
|
139
139
|
throw new Error('Controller not initialized');
|
|
140
140
|
}
|
|
141
|
-
this.zwaveController.setNodeName(nodeId, name);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
accessory.rename(name);
|
|
141
|
+
await this.zwaveController.setNodeName(nodeId, name);
|
|
142
|
+
const node = this.zwaveController.nodes.get(nodeId);
|
|
143
|
+
if (node) {
|
|
144
|
+
this.recreateNodeAccessory(node, Date.now().toString(36));
|
|
146
145
|
}
|
|
147
146
|
return sendJson({ success: true });
|
|
148
147
|
}
|
|
@@ -382,6 +381,27 @@ class ZWaveUsbPlatform {
|
|
|
382
381
|
}
|
|
383
382
|
}
|
|
384
383
|
}
|
|
384
|
+
recreateNodeAccessory(node, uuidSeed) {
|
|
385
|
+
const existing = this.zwaveAccessories.get(node.nodeId);
|
|
386
|
+
if (existing) {
|
|
387
|
+
existing.stop();
|
|
388
|
+
this.api.unregisterPlatformAccessories('homebridge-zwave-usb', 'ZWaveUSB', [
|
|
389
|
+
existing.platformAccessory,
|
|
390
|
+
]);
|
|
391
|
+
this.zwaveAccessories.delete(node.nodeId);
|
|
392
|
+
const index = this.accessories.indexOf(existing.platformAccessory);
|
|
393
|
+
if (index !== -1) {
|
|
394
|
+
this.accessories.splice(index, 1);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const homeId = this.zwaveController?.homeId;
|
|
398
|
+
if (!homeId) {
|
|
399
|
+
throw new Error(`Cannot recreate accessory for Node ${node.nodeId} - Home ID not available`);
|
|
400
|
+
}
|
|
401
|
+
const recreated = AccessoryFactory_1.AccessoryFactory.create(this, node, homeId, { forceUuidSeed: uuidSeed });
|
|
402
|
+
this.zwaveAccessories.set(node.nodeId, recreated);
|
|
403
|
+
recreated.initialize();
|
|
404
|
+
}
|
|
385
405
|
/**
|
|
386
406
|
* Event-Driven Updates:
|
|
387
407
|
* Instead of refreshing every accessory on every change, we route the specific
|
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.
|
|
4
|
+
"version": "3.4.0",
|
|
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": {
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
},
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
|
-
"url": "https://github.com/sapireli/homebridge-zwave-usb
|
|
17
|
+
"url": "https://github.com/sapireli/homebridge-zwave-usb"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"registry": "https://registry.npmjs.org/",
|
|
21
|
+
"access": "public"
|
|
18
22
|
},
|
|
19
23
|
"main": "dist/index.js",
|
|
20
24
|
"scripts": {
|