homebridge-zwave-usb 1.0.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/LICENSE +21 -0
- package/README.md +135 -0
- package/config.schema.json +128 -0
- package/dist/accessories/AccessoryFactory.d.ts +7 -0
- package/dist/accessories/AccessoryFactory.js +76 -0
- package/dist/accessories/ControllerAccessory.d.ts +18 -0
- package/dist/accessories/ControllerAccessory.js +116 -0
- package/dist/accessories/ZWaveAccessory.d.ts +20 -0
- package/dist/accessories/ZWaveAccessory.js +102 -0
- package/dist/features/BinarySwitchFeature.d.ts +8 -0
- package/dist/features/BinarySwitchFeature.js +42 -0
- package/dist/features/CentralSceneFeature.d.ts +7 -0
- package/dist/features/CentralSceneFeature.js +58 -0
- package/dist/features/ContactSensorFeature.d.ts +8 -0
- package/dist/features/ContactSensorFeature.js +49 -0
- package/dist/features/LeakSensorFeature.d.ts +8 -0
- package/dist/features/LeakSensorFeature.js +51 -0
- package/dist/features/LockFeature.d.ts +10 -0
- package/dist/features/LockFeature.js +77 -0
- package/dist/features/MotionSensorFeature.d.ts +8 -0
- package/dist/features/MotionSensorFeature.js +51 -0
- package/dist/features/MultilevelSensorFeature.d.ts +11 -0
- package/dist/features/MultilevelSensorFeature.js +70 -0
- package/dist/features/MultilevelSwitchFeature.d.ts +10 -0
- package/dist/features/MultilevelSwitchFeature.js +74 -0
- package/dist/features/ZWaveFeature.d.ts +18 -0
- package/dist/features/ZWaveFeature.js +37 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/platform/ZWaveUsbPlatform.d.ts +19 -0
- package/dist/platform/ZWaveUsbPlatform.js +133 -0
- package/dist/platform/settings.d.ts +2 -0
- package/dist/platform/settings.js +5 -0
- package/dist/zwave/ZWaveController.d.ts +38 -0
- package/dist/zwave/ZWaveController.js +170 -0
- package/dist/zwave/ZWaveRemoteController.d.ts +47 -0
- package/dist/zwave/ZWaveRemoteController.js +278 -0
- package/dist/zwave/interfaces.d.ts +29 -0
- package/dist/zwave/interfaces.js +2 -0
- package/homebridge-ui/public/index.html +165 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eliran Sapir
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Homebridge Z-Wave USB
|
|
2
|
+
|
|
3
|
+
[](https://github.com/user/homebridge-zwave-usb/actions/workflows/build.yml)
|
|
4
|
+
|
|
5
|
+
A high-performance, native HomeKit integration for Z-Wave networks.
|
|
6
|
+
|
|
7
|
+
Unlike legacy plugins, **Homebridge Z-Wave USB** is built on the modern **Z-Wave JS** driver (the same stack used by Home Assistant), offering unmatched speed, reliability, and device support.
|
|
8
|
+
|
|
9
|
+
## Why this plugin?
|
|
10
|
+
|
|
11
|
+
* 🚀 **Performance:** Direct communication with your USB stick. No intermediate MQTT brokers or slow translation layers.
|
|
12
|
+
* 🔌 **Flexibility:** Run it standalone ("Direct Mode") OR connect it to an existing **Z-Wave JS UI** instance ("Remote Mode").
|
|
13
|
+
* 🏠 **Native Feel:** Devices appear in Apple Home exactly as you expect. Multi-sensor devices are grouped logically.
|
|
14
|
+
* 🔐 **Secure:** Full support for S2 Security (Locks, Garage Doors) with auto-generated keys.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🛠️ One Plugin, Three Architectures
|
|
19
|
+
|
|
20
|
+
Choose the setup that fits your needs:
|
|
21
|
+
|
|
22
|
+
| Mode | Best For... | Description |
|
|
23
|
+
| :--- | :--- | :--- |
|
|
24
|
+
| **Direct** | ⚡ **Speed & Simplicity** | The plugin owns the USB stick. Zero external dependencies. Ideal for Raspberry Pi setups. |
|
|
25
|
+
| **Remote** | 🐳 **Docker / NAS** | Connects to **Z-Wave JS UI** running in a separate container. Perfect if you want the advanced Z-Wave control panel. |
|
|
26
|
+
| **Host** | 🎛️ **Hybrid** | The plugin runs the Z-Wave network but exposes a server for **Z-Wave JS UI** to connect to for management/graphs. |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 📦 Installation
|
|
31
|
+
|
|
32
|
+
1. **Install Homebridge**: [Official Instructions](https://github.com/homebridge/homebridge/wiki).
|
|
33
|
+
2. **Install Plugin**:
|
|
34
|
+
```bash
|
|
35
|
+
npm install -g homebridge-zwave-usb
|
|
36
|
+
```
|
|
37
|
+
3. **Permissions (Linux/Pi)**: Ensure the user running Homebridge has access to serial ports:
|
|
38
|
+
```bash
|
|
39
|
+
sudo usermod -a -G dialout $(whoami)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## ⚙️ Configuration
|
|
45
|
+
|
|
46
|
+
### Option A: Direct Mode (Recommended)
|
|
47
|
+
*The simplest way to get started.*
|
|
48
|
+
|
|
49
|
+
1. Go to **Settings** in Homebridge UI.
|
|
50
|
+
2. **Serial Port**: Enter the persistent path to your stick.
|
|
51
|
+
> [!WARNING]
|
|
52
|
+
> **Do not use `/dev/ttyACM0`**. Use the stable path found in `/dev/serial/by-id/...`
|
|
53
|
+
3. **Security Keys**: Click the **"Auto-Generate Keys"** button.
|
|
54
|
+
4. **Save & Restart**.
|
|
55
|
+
|
|
56
|
+
### Option B: Remote Mode (Z-Wave JS UI)
|
|
57
|
+
*Connect to an existing Z-Wave JS UI instance.*
|
|
58
|
+
|
|
59
|
+
1. Ensure **Z-Wave JS UI** is running and "WS Server" is enabled (Settings -> Home Assistant / WS).
|
|
60
|
+
2. In Homebridge Plugin Settings:
|
|
61
|
+
* **Serial Port**: Enter the WebSocket URL (e.g., `ws://localhost:3000` or `ws://192.168.1.50:3000`).
|
|
62
|
+
3. **Keys**: Leave blank. (Keys are managed inside Z-Wave JS UI).
|
|
63
|
+
4. **Save & Restart**.
|
|
64
|
+
|
|
65
|
+
### Option C: Host Mode
|
|
66
|
+
*Homebridge runs the network, but you want to view the graph in Z-Wave JS UI.*
|
|
67
|
+
|
|
68
|
+
1. Follow "Direct Mode" setup.
|
|
69
|
+
2. Check **"Enable Z-Wave Server"**.
|
|
70
|
+
3. In **Z-Wave JS UI**, set the driver mode to "Remote" and point it to `ws://YOUR_HOMEBRIDGE_IP:3000`.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## ✅ Supported Devices
|
|
75
|
+
|
|
76
|
+
The plugin automatically maps Z-Wave capabilities to Apple HomeKit services:
|
|
77
|
+
|
|
78
|
+
| Device Type | HomeKit Service | Notes |
|
|
79
|
+
| :--- | :--- | :--- |
|
|
80
|
+
| **Switches / Plugs** | Switch / Outlet | Instant status updates. |
|
|
81
|
+
| **Dimmers** | Lightbulb | Brightness 0-100%. |
|
|
82
|
+
| **Locks** | Lock Mechanism | Supports Secure (S0/S2) pairing. |
|
|
83
|
+
| **Motion Sensors** | Motion Sensor | |
|
|
84
|
+
| **Door/Window** | Contact Sensor | |
|
|
85
|
+
| **Leak Sensors** | Leak Sensor | |
|
|
86
|
+
| **Climate** | Temp / Humidity | Auto-converts Fahrenheit/Celsius. |
|
|
87
|
+
| **Scene Controllers** | Programmable Switch | Supports Single, Double, Long press. |
|
|
88
|
+
| **Battery** | Battery Service | Low battery alerts in Home app. |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 🎮 Managing Your Network
|
|
93
|
+
|
|
94
|
+
This plugin exposes a **"Z-Wave Controller"** accessory in Apple Home. You can use it to manage your mesh right from your phone:
|
|
95
|
+
|
|
96
|
+
* **Inclusion Mode:** Turn ON to pair a new device (timeout: 60s).
|
|
97
|
+
* **Exclusion Mode:** Turn ON to unpair/remove a device.
|
|
98
|
+
* **Heal Network:** Turn ON to rebuild mesh routes (do this after moving devices).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## ❓ Troubleshooting
|
|
103
|
+
|
|
104
|
+
* **Device not showing up?** Battery devices sleep. Wake them up (press the button) repeatedly after pairing until the interview completes.
|
|
105
|
+
* **Port Busy Error?** Ensure no other software (like Z-Wave JS UI) is trying to use the USB stick at the same time. If you want both, use **Remote Mode**.
|
|
106
|
+
* **Secure Inclusion Failed?** Ensure you have generated Security Keys in the settings.
|
|
107
|
+
|
|
108
|
+
## Contributing
|
|
109
|
+
|
|
110
|
+
Pull requests are welcome! Please ensure all changes pass `npm test` and `npm run build`.
|
|
111
|
+
|
|
112
|
+
## Legal & Disclaimer
|
|
113
|
+
|
|
114
|
+
**Homebridge Z-Wave USB** is an independent project and is not affiliated with, endorsed by, or sponsored by any of the following trademark owners:
|
|
115
|
+
|
|
116
|
+
- **Apple, HomeKit, and Apple Home** are trademarks of Apple Inc., registered in the U.S. and other countries.
|
|
117
|
+
- **Z-Wave and Z-Wave Plus** are registered trademarks of Silicon Labs and its subsidiaries.
|
|
118
|
+
- **Homebridge** is a registered trademark of the Homebridge project.
|
|
119
|
+
- **Aeotec, Zooz, and Home Assistant** are trademarks of their respective owners.
|
|
120
|
+
|
|
121
|
+
This project is **not affiliated with, maintained, or supported by** the official **Z-Wave JS** or **Z-Wave JS UI** (formerly zwavejs2mqtt) projects. It is an independent wrapper for Homebridge.
|
|
122
|
+
|
|
123
|
+
**DISCLAIMER:**
|
|
124
|
+
This software is provided "as is", without warranty of any kind. The authors are not responsible for any damage, data loss, or security breaches that may occur.
|
|
125
|
+
**You use this software at your own risk.**
|
|
126
|
+
This includes, but is not limited to, the risk of:
|
|
127
|
+
- Smart locks unlocking unexpectedly.
|
|
128
|
+
- Z-Wave devices failing to respond.
|
|
129
|
+
- Loss of configuration data.
|
|
130
|
+
|
|
131
|
+
Always ensure you have backups of your Z-Wave controller keys and Homebridge configuration.
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pluginAlias": "ZWaveUSB",
|
|
3
|
+
"pluginType": "platform",
|
|
4
|
+
"customUi": true,
|
|
5
|
+
"schema": {
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"name": {
|
|
9
|
+
"title": "Name",
|
|
10
|
+
"type": "string",
|
|
11
|
+
"required": true,
|
|
12
|
+
"default": "Z-Wave USB"
|
|
13
|
+
},
|
|
14
|
+
"platform": {
|
|
15
|
+
"title": "Platform",
|
|
16
|
+
"type": "string",
|
|
17
|
+
"required": true,
|
|
18
|
+
"default": "ZWaveUSB",
|
|
19
|
+
"const": "ZWaveUSB"
|
|
20
|
+
},
|
|
21
|
+
"serialPort": {
|
|
22
|
+
"title": "Serial Port or WebSocket URL",
|
|
23
|
+
"type": "string",
|
|
24
|
+
"required": true,
|
|
25
|
+
"description": "Path to the serial port (e.g., /dev/ttyACM0) or WebSocket URL (e.g., ws://localhost:3000)."
|
|
26
|
+
},
|
|
27
|
+
"securityKeys": {
|
|
28
|
+
"title": "Security Keys",
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"S0_Legacy": {
|
|
32
|
+
"title": "S0 Legacy Key",
|
|
33
|
+
"type": "string",
|
|
34
|
+
"minLength": 32,
|
|
35
|
+
"maxLength": 32,
|
|
36
|
+
"pattern": "^[0-9a-fA-F]+$",
|
|
37
|
+
"description": "16-byte hex string (32 characters). REQUIRED for legacy secure devices."
|
|
38
|
+
},
|
|
39
|
+
"S2_Unauthenticated": {
|
|
40
|
+
"title": "S2 Unauthenticated Key",
|
|
41
|
+
"type": "string",
|
|
42
|
+
"minLength": 32,
|
|
43
|
+
"maxLength": 32,
|
|
44
|
+
"pattern": "^[0-9a-fA-F]+$",
|
|
45
|
+
"description": "16-byte hex string (32 characters)."
|
|
46
|
+
},
|
|
47
|
+
"S2_Authenticated": {
|
|
48
|
+
"title": "S2 Authenticated Key",
|
|
49
|
+
"type": "string",
|
|
50
|
+
"minLength": 32,
|
|
51
|
+
"maxLength": 32,
|
|
52
|
+
"pattern": "^[0-9a-fA-F]+$",
|
|
53
|
+
"description": "16-byte hex string (32 characters)."
|
|
54
|
+
},
|
|
55
|
+
"S2_AccessControl": {
|
|
56
|
+
"title": "S2 Access Control Key",
|
|
57
|
+
"type": "string",
|
|
58
|
+
"minLength": 32,
|
|
59
|
+
"maxLength": 32,
|
|
60
|
+
"pattern": "^[0-9a-fA-F]+$",
|
|
61
|
+
"description": "16-byte hex string (32 characters)."
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"inclusionTimeoutSeconds": {
|
|
66
|
+
"title": "Inclusion Timeout (Seconds)",
|
|
67
|
+
"type": "integer",
|
|
68
|
+
"default": 60,
|
|
69
|
+
"minimum": 30,
|
|
70
|
+
"maximum": 300,
|
|
71
|
+
"description": "How long the inclusion mode stays active."
|
|
72
|
+
},
|
|
73
|
+
"enableServer": {
|
|
74
|
+
"title": "Enable Z-Wave Server (Host Mode)",
|
|
75
|
+
"type": "boolean",
|
|
76
|
+
"default": false,
|
|
77
|
+
"description": "Expose the Z-Wave Driver via WebSocket. Allows Z-Wave JS UI to connect to this plugin."
|
|
78
|
+
},
|
|
79
|
+
"serverPort": {
|
|
80
|
+
"title": "Server Port",
|
|
81
|
+
"type": "integer",
|
|
82
|
+
"default": 3000,
|
|
83
|
+
"minimum": 1024,
|
|
84
|
+
"maximum": 65535,
|
|
85
|
+
"description": "Port for the Z-Wave WebSocket Server (only if enabled).",
|
|
86
|
+
"condition": {
|
|
87
|
+
"function": {
|
|
88
|
+
"enableServer": true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"debug": {
|
|
93
|
+
"title": "Enable Debug Logging",
|
|
94
|
+
"type": "boolean",
|
|
95
|
+
"default": false,
|
|
96
|
+
"description": "If true, enables verbose logging for troubleshooting."
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
"layout": [
|
|
101
|
+
"name",
|
|
102
|
+
{
|
|
103
|
+
"key": "serialPort",
|
|
104
|
+
"description": "WARNING: Do not use /dev/ttyUSBX or /dev/ttyACMX serial devices, as those mappings can change over time. Instead, use the /dev/serial/by-id/X persistent path for your Z-Wave stick. On macOS, use /dev/tty.usbmodem..."
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"type": "fieldset",
|
|
108
|
+
"title": "Z-Wave Security Keys",
|
|
109
|
+
"expandable": true,
|
|
110
|
+
"description": "These keys are used to connect securely to compatible devices. \n\nDirect/Host Mode: Use the 'Auto-Generate Keys' button in the settings UI.\n\nRemote Mode: These keys are IGNORED (set them in Z-Wave JS UI instead).\n\nWARNING: Backup your keys! If you lose them, you will have to factory reset your devices to re-pair them.",
|
|
111
|
+
"items": [
|
|
112
|
+
"securityKeys.S0_Legacy",
|
|
113
|
+
"securityKeys.S2_Unauthenticated",
|
|
114
|
+
"securityKeys.S2_Authenticated",
|
|
115
|
+
"securityKeys.S2_AccessControl"
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"type": "fieldset",
|
|
120
|
+
"title": "Advanced",
|
|
121
|
+
"expandable": true,
|
|
122
|
+
"items": [
|
|
123
|
+
"inclusionTimeoutSeconds",
|
|
124
|
+
"debug"
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { IZWaveNode } from '../zwave/interfaces';
|
|
2
|
+
import { ZWaveAccessory } from './ZWaveAccessory';
|
|
3
|
+
import { ZWaveUsbPlatform } from '../platform/ZWaveUsbPlatform';
|
|
4
|
+
export declare class AccessoryFactory {
|
|
5
|
+
static create(platform: ZWaveUsbPlatform, node: IZWaveNode, homeId: number): ZWaveAccessory;
|
|
6
|
+
private static attachFeatures;
|
|
7
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AccessoryFactory = void 0;
|
|
4
|
+
const ZWaveAccessory_1 = require("./ZWaveAccessory");
|
|
5
|
+
const BinarySwitchFeature_1 = require("../features/BinarySwitchFeature");
|
|
6
|
+
const MultilevelSwitchFeature_1 = require("../features/MultilevelSwitchFeature");
|
|
7
|
+
const LockFeature_1 = require("../features/LockFeature");
|
|
8
|
+
const MultilevelSensorFeature_1 = require("../features/MultilevelSensorFeature");
|
|
9
|
+
const ContactSensorFeature_1 = require("../features/ContactSensorFeature");
|
|
10
|
+
const MotionSensorFeature_1 = require("../features/MotionSensorFeature");
|
|
11
|
+
const LeakSensorFeature_1 = require("../features/LeakSensorFeature");
|
|
12
|
+
const CentralSceneFeature_1 = require("../features/CentralSceneFeature");
|
|
13
|
+
class AccessoryFactory {
|
|
14
|
+
static create(platform, node, homeId) {
|
|
15
|
+
const accessory = new ZWaveAccessory_1.ZWaveAccessory(platform, node, homeId);
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
const endpoints = node.getAllEndpoints();
|
|
18
|
+
for (const endpoint of endpoints) {
|
|
19
|
+
// Endpoint interface from zwave-js might differ from what we have in virtual node
|
|
20
|
+
this.attachFeatures(platform, accessory, node, endpoint);
|
|
21
|
+
}
|
|
22
|
+
return accessory;
|
|
23
|
+
}
|
|
24
|
+
static attachFeatures(platform, accessory, node, endpoint) {
|
|
25
|
+
const allValues = node.getDefinedValueIDs();
|
|
26
|
+
const values = allValues.filter(v => v.endpoint === endpoint.index);
|
|
27
|
+
const hasSwitch = endpoint.supportsCC(37);
|
|
28
|
+
const hasMultilevelSwitch = endpoint.supportsCC(38);
|
|
29
|
+
const hasLock = endpoint.supportsCC(98);
|
|
30
|
+
const hasSensorMultilevel = endpoint.supportsCC(49);
|
|
31
|
+
const hasNotification = endpoint.supportsCC(113);
|
|
32
|
+
const hasSensorBinary = endpoint.supportsCC(48);
|
|
33
|
+
const hasCentralScene = endpoint.supportsCC(91);
|
|
34
|
+
// 1. Lock
|
|
35
|
+
if (hasLock) {
|
|
36
|
+
accessory.addFeature(new LockFeature_1.LockFeature(platform, accessory.platformAccessory, endpoint));
|
|
37
|
+
}
|
|
38
|
+
// 2. Multilevel Switch (Dimmer)
|
|
39
|
+
if (hasMultilevelSwitch) {
|
|
40
|
+
accessory.addFeature(new MultilevelSwitchFeature_1.MultilevelSwitchFeature(platform, accessory.platformAccessory, endpoint));
|
|
41
|
+
}
|
|
42
|
+
// 3. Binary Switch
|
|
43
|
+
if (hasSwitch && !hasMultilevelSwitch) {
|
|
44
|
+
accessory.addFeature(new BinarySwitchFeature_1.BinarySwitchFeature(platform, accessory.platformAccessory, endpoint));
|
|
45
|
+
}
|
|
46
|
+
// 4. Multilevel Sensor
|
|
47
|
+
if (hasSensorMultilevel) {
|
|
48
|
+
accessory.addFeature(new MultilevelSensorFeature_1.MultilevelSensorFeature(platform, accessory.platformAccessory, endpoint));
|
|
49
|
+
}
|
|
50
|
+
// 5. Notification Sensors
|
|
51
|
+
if (hasNotification) {
|
|
52
|
+
// Water Alarm
|
|
53
|
+
if (values.some((v) => v.commandClass === 113 && (v.property === 'Water Alarm' || v.propertyKey === 'Water leak status'))) {
|
|
54
|
+
accessory.addFeature(new LeakSensorFeature_1.LeakSensorFeature(platform, accessory.platformAccessory, endpoint));
|
|
55
|
+
}
|
|
56
|
+
// Home Security - Motion
|
|
57
|
+
if (values.some((v) => v.commandClass === 113 && (v.property === 'Home Security' || v.propertyKey === 'Motion sensor status'))) {
|
|
58
|
+
accessory.addFeature(new MotionSensorFeature_1.MotionSensorFeature(platform, accessory.platformAccessory, endpoint));
|
|
59
|
+
}
|
|
60
|
+
// Access Control - Door/Window
|
|
61
|
+
if (values.some((v) => v.commandClass === 113 && (v.property === 'Access Control' || v.propertyKey === 'Door status'))) {
|
|
62
|
+
accessory.addFeature(new ContactSensorFeature_1.ContactSensorFeature(platform, accessory.platformAccessory, endpoint));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// 6. Binary Sensor (Legacy)
|
|
66
|
+
if (hasSensorBinary) {
|
|
67
|
+
// Default to ContactSensor for generic binary sensors
|
|
68
|
+
accessory.addFeature(new ContactSensorFeature_1.ContactSensorFeature(platform, accessory.platformAccessory, endpoint));
|
|
69
|
+
}
|
|
70
|
+
// 7. Central Scene (Buttons)
|
|
71
|
+
if (hasCentralScene) {
|
|
72
|
+
accessory.addFeature(new CentralSceneFeature_1.CentralSceneFeature(platform, accessory.platformAccessory, endpoint));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.AccessoryFactory = AccessoryFactory;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PlatformAccessory } from 'homebridge';
|
|
2
|
+
import { ZWaveUsbPlatform } from '../platform/ZWaveUsbPlatform';
|
|
3
|
+
import { IZWaveController } from '../zwave/interfaces';
|
|
4
|
+
export declare class ControllerAccessory {
|
|
5
|
+
private readonly platform;
|
|
6
|
+
private readonly controller;
|
|
7
|
+
private inclusionService;
|
|
8
|
+
private exclusionService;
|
|
9
|
+
private healService;
|
|
10
|
+
readonly platformAccessory: PlatformAccessory;
|
|
11
|
+
constructor(platform: ZWaveUsbPlatform, controller: IZWaveController);
|
|
12
|
+
private handleSetInclusion;
|
|
13
|
+
private handleGetInclusion;
|
|
14
|
+
private handleSetExclusion;
|
|
15
|
+
private handleGetExclusion;
|
|
16
|
+
private handleSetHeal;
|
|
17
|
+
private handleGetHeal;
|
|
18
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ControllerAccessory = void 0;
|
|
4
|
+
class ControllerAccessory {
|
|
5
|
+
platform;
|
|
6
|
+
controller;
|
|
7
|
+
inclusionService;
|
|
8
|
+
exclusionService;
|
|
9
|
+
healService;
|
|
10
|
+
platformAccessory;
|
|
11
|
+
constructor(platform, controller) {
|
|
12
|
+
this.platform = platform;
|
|
13
|
+
this.controller = controller;
|
|
14
|
+
const homeId = this.controller.homeId;
|
|
15
|
+
const uuid = this.platform.api.hap.uuid.generate(`ZWaveController-${homeId}`);
|
|
16
|
+
const existingAccessory = this.platform.accessories.find((accessory) => accessory.UUID === uuid);
|
|
17
|
+
if (existingAccessory) {
|
|
18
|
+
this.platformAccessory = existingAccessory;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
this.platform.log.info('Creating new Z-Wave Controller accessory');
|
|
22
|
+
this.platformAccessory = new this.platform.api.platformAccessory('Z-Wave Controller', uuid);
|
|
23
|
+
this.platform.api.registerPlatformAccessories('homebridge-zwave-usb', 'ZWaveUSB', [this.platformAccessory]);
|
|
24
|
+
this.platform.accessories.push(this.platformAccessory);
|
|
25
|
+
}
|
|
26
|
+
this.inclusionService =
|
|
27
|
+
this.platformAccessory.getService('Inclusion Mode') ||
|
|
28
|
+
this.platformAccessory.addService(this.platform.Service.Switch, 'Inclusion Mode', 'Inclusion');
|
|
29
|
+
this.exclusionService =
|
|
30
|
+
this.platformAccessory.getService('Exclusion Mode') ||
|
|
31
|
+
this.platformAccessory.addService(this.platform.Service.Switch, 'Exclusion Mode', 'Exclusion');
|
|
32
|
+
this.healService =
|
|
33
|
+
this.platformAccessory.getService('Heal Network') ||
|
|
34
|
+
this.platformAccessory.addService(this.platform.Service.Switch, 'Heal Network', 'Heal');
|
|
35
|
+
this.inclusionService
|
|
36
|
+
.getCharacteristic(this.platform.Characteristic.On)
|
|
37
|
+
.on('set', this.handleSetInclusion.bind(this))
|
|
38
|
+
.on('get', this.handleGetInclusion.bind(this));
|
|
39
|
+
this.exclusionService
|
|
40
|
+
.getCharacteristic(this.platform.Characteristic.On)
|
|
41
|
+
.on('set', this.handleSetExclusion.bind(this))
|
|
42
|
+
.on('get', this.handleGetExclusion.bind(this));
|
|
43
|
+
this.healService
|
|
44
|
+
.getCharacteristic(this.platform.Characteristic.On)
|
|
45
|
+
.on('set', this.handleSetHeal.bind(this))
|
|
46
|
+
.on('get', this.handleGetHeal.bind(this));
|
|
47
|
+
// Listen for controller events to sync state
|
|
48
|
+
this.controller.on('inclusion started', () => {
|
|
49
|
+
this.platform.log.info('Controller event: Inclusion Started');
|
|
50
|
+
this.inclusionService.updateCharacteristic(this.platform.Characteristic.On, true);
|
|
51
|
+
this.exclusionService.updateCharacteristic(this.platform.Characteristic.On, false);
|
|
52
|
+
});
|
|
53
|
+
this.controller.on('inclusion stopped', () => {
|
|
54
|
+
this.platform.log.info('Controller event: Inclusion Stopped');
|
|
55
|
+
this.inclusionService.updateCharacteristic(this.platform.Characteristic.On, false);
|
|
56
|
+
});
|
|
57
|
+
this.controller.on('exclusion started', () => {
|
|
58
|
+
this.platform.log.info('Controller event: Exclusion Started');
|
|
59
|
+
this.exclusionService.updateCharacteristic(this.platform.Characteristic.On, true);
|
|
60
|
+
this.inclusionService.updateCharacteristic(this.platform.Characteristic.On, false);
|
|
61
|
+
});
|
|
62
|
+
this.controller.on('exclusion stopped', () => {
|
|
63
|
+
this.platform.log.info('Controller event: Exclusion Stopped');
|
|
64
|
+
this.exclusionService.updateCharacteristic(this.platform.Characteristic.On, false);
|
|
65
|
+
});
|
|
66
|
+
this.controller.on('heal network done', () => {
|
|
67
|
+
this.platform.log.info('Controller event: Heal Network Done');
|
|
68
|
+
this.healService.updateCharacteristic(this.platform.Characteristic.On, false);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async handleSetInclusion(value, callback) {
|
|
72
|
+
if (value) {
|
|
73
|
+
this.platform.log.info('Requesting Inclusion Mode ON');
|
|
74
|
+
// We rely on the event listener to update the characteristic
|
|
75
|
+
// But for UI responsiveness, we might want to acknowledge the command
|
|
76
|
+
await this.controller.startInclusion();
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.platform.log.info('Requesting Inclusion Mode OFF');
|
|
80
|
+
await this.controller.stopInclusion();
|
|
81
|
+
}
|
|
82
|
+
callback(null);
|
|
83
|
+
}
|
|
84
|
+
async handleGetInclusion(callback) {
|
|
85
|
+
callback(null, false);
|
|
86
|
+
}
|
|
87
|
+
async handleSetExclusion(value, callback) {
|
|
88
|
+
if (value) {
|
|
89
|
+
this.platform.log.info('Requesting Exclusion Mode ON');
|
|
90
|
+
await this.controller.startExclusion();
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
this.platform.log.info('Requesting Exclusion Mode OFF');
|
|
94
|
+
await this.controller.stopExclusion();
|
|
95
|
+
}
|
|
96
|
+
callback(null);
|
|
97
|
+
}
|
|
98
|
+
async handleGetExclusion(callback) {
|
|
99
|
+
callback(null, false);
|
|
100
|
+
}
|
|
101
|
+
async handleSetHeal(value, callback) {
|
|
102
|
+
if (value) {
|
|
103
|
+
this.platform.log.info('Requesting Heal Network ON');
|
|
104
|
+
await this.controller.startHealing();
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
this.platform.log.info('Requesting Heal Network OFF');
|
|
108
|
+
await this.controller.stopHealing();
|
|
109
|
+
}
|
|
110
|
+
callback(null);
|
|
111
|
+
}
|
|
112
|
+
async handleGetHeal(callback) {
|
|
113
|
+
callback(null, false);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.ControllerAccessory = ControllerAccessory;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PlatformAccessory, Service } from 'homebridge';
|
|
2
|
+
import { IZWaveNode } from '../zwave/interfaces';
|
|
3
|
+
import { ZWaveUsbPlatform } from '../platform/ZWaveUsbPlatform';
|
|
4
|
+
import { ZWaveFeature } from '../features/ZWaveFeature';
|
|
5
|
+
export declare class ZWaveAccessory {
|
|
6
|
+
readonly platform: ZWaveUsbPlatform;
|
|
7
|
+
readonly node: IZWaveNode;
|
|
8
|
+
readonly homeId: number;
|
|
9
|
+
readonly platformAccessory: PlatformAccessory;
|
|
10
|
+
private features;
|
|
11
|
+
protected batteryService: Service | undefined;
|
|
12
|
+
private initialized;
|
|
13
|
+
constructor(platform: ZWaveUsbPlatform, node: IZWaveNode, homeId: number);
|
|
14
|
+
addFeature(feature: ZWaveFeature): void;
|
|
15
|
+
initialize(): void;
|
|
16
|
+
refresh(): void;
|
|
17
|
+
protected updateBattery(): void;
|
|
18
|
+
private handleGetBatteryLevel;
|
|
19
|
+
private handleGetStatusLowBattery;
|
|
20
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ZWaveAccessory = void 0;
|
|
4
|
+
class ZWaveAccessory {
|
|
5
|
+
platform;
|
|
6
|
+
node;
|
|
7
|
+
homeId;
|
|
8
|
+
platformAccessory;
|
|
9
|
+
features = [];
|
|
10
|
+
batteryService;
|
|
11
|
+
initialized = false;
|
|
12
|
+
constructor(platform, node, homeId) {
|
|
13
|
+
this.platform = platform;
|
|
14
|
+
this.node = node;
|
|
15
|
+
this.homeId = homeId;
|
|
16
|
+
const uuid = this.platform.api.hap.uuid.generate(`homebridge-zwave-usb-${this.homeId}-${this.node.nodeId}`);
|
|
17
|
+
const existingAccessory = this.platform.accessories.find((accessory) => accessory.UUID === uuid);
|
|
18
|
+
if (existingAccessory) {
|
|
19
|
+
this.platformAccessory = existingAccessory;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const accessoryName = `Node ${this.node.nodeId}`;
|
|
23
|
+
this.platform.log.info(`Creating new accessory for ${accessoryName}`);
|
|
24
|
+
this.platformAccessory = new this.platform.api.platformAccessory(accessoryName, uuid);
|
|
25
|
+
this.platform.api.registerPlatformAccessories('homebridge-zwave-usb', 'ZWaveUSB', [this.platformAccessory]);
|
|
26
|
+
this.platform.accessories.push(this.platformAccessory);
|
|
27
|
+
}
|
|
28
|
+
// Battery Service (Native support)
|
|
29
|
+
if (this.node.supportsCC(128)) { // Battery CC
|
|
30
|
+
this.batteryService =
|
|
31
|
+
this.platformAccessory.getService(this.platform.Service.Battery) ||
|
|
32
|
+
this.platformAccessory.addService(this.platform.Service.Battery);
|
|
33
|
+
this.batteryService
|
|
34
|
+
.getCharacteristic(this.platform.Characteristic.BatteryLevel)
|
|
35
|
+
.on('get', this.handleGetBatteryLevel.bind(this));
|
|
36
|
+
this.batteryService
|
|
37
|
+
.getCharacteristic(this.platform.Characteristic.StatusLowBattery)
|
|
38
|
+
.on('get', this.handleGetStatusLowBattery.bind(this));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
addFeature(feature) {
|
|
42
|
+
this.features.push(feature);
|
|
43
|
+
}
|
|
44
|
+
initialize() {
|
|
45
|
+
if (this.initialized) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.initialized = true;
|
|
49
|
+
// Initialize all features
|
|
50
|
+
for (const feature of this.features) {
|
|
51
|
+
feature.init();
|
|
52
|
+
}
|
|
53
|
+
this.refresh();
|
|
54
|
+
}
|
|
55
|
+
refresh() {
|
|
56
|
+
this.updateBattery();
|
|
57
|
+
for (const feature of this.features) {
|
|
58
|
+
feature.update();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
updateBattery() {
|
|
62
|
+
if (!this.batteryService || !this.node.supportsCC(128)) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const value = this.node.getValue({
|
|
66
|
+
commandClass: 128,
|
|
67
|
+
property: 'level',
|
|
68
|
+
});
|
|
69
|
+
if (typeof value === 'number') {
|
|
70
|
+
this.batteryService.updateCharacteristic(this.platform.Characteristic.BatteryLevel, value);
|
|
71
|
+
this.batteryService.updateCharacteristic(this.platform.Characteristic.StatusLowBattery, value <= 20
|
|
72
|
+
? this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
73
|
+
: this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async handleGetBatteryLevel(callback) {
|
|
77
|
+
if (!this.node.supportsCC(128)) {
|
|
78
|
+
callback(null, 100);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const value = this.node.getValue({
|
|
82
|
+
commandClass: 128,
|
|
83
|
+
property: 'level',
|
|
84
|
+
});
|
|
85
|
+
callback(null, value || 100);
|
|
86
|
+
}
|
|
87
|
+
async handleGetStatusLowBattery(callback) {
|
|
88
|
+
if (!this.node.supportsCC(128)) {
|
|
89
|
+
callback(null, this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const value = this.node.getValue({
|
|
93
|
+
commandClass: 128,
|
|
94
|
+
property: 'level',
|
|
95
|
+
});
|
|
96
|
+
const level = value || 100;
|
|
97
|
+
callback(null, level <= 20
|
|
98
|
+
? this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW
|
|
99
|
+
: this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
exports.ZWaveAccessory = ZWaveAccessory;
|