Overview
Z-Wave is a wireless communication protocol designed specifically for home automation and IoT, operating in the sub-GHz frequency band (908.42 MHz in North America, 868.42 MHz in Europe) to avoid interference with WiFi and Zigbee devices. It uses a mesh networking topology where mains-powered devices act as repeaters, extending the network range. Z-Wave supports up to 232 devices per network and provides AES-128 encryption (Security S0 and S2 frameworks) for secure communication between devices such as smart locks, sensors, switches, thermostats, and garage door controllers.
The Z-Wave ecosystem includes certified interoperable devices from hundreds of manufacturers, all managed through a Z-Wave controller (USB stick or hub). Open-source tools like Z-Wave JS (the modern standard), OpenZWave (legacy), and the Zniffer protocol analyzer enable developers and home automation enthusiasts to build custom integrations. Z-Wave JS is the recommended library, providing a complete implementation of the Z-Wave protocol with support for Home Assistant’s Z-Wave JS integration, making it the de facto way to integrate Z-Wave into open-source smart home platforms.
Installation
Z-Wave JS (Recommended)
# Install Z-Wave JS Server
npm install -g @zwave-js/server
# Run Z-Wave JS Server
zwave-server /dev/ttyUSB0
# Or using Docker
docker run -d \
--name zwavejs \
--restart=unless-stopped \
--device /dev/ttyUSB0:/dev/ttyUSB0 \
-p 3000:3000 \
-p 8091:8091 \
-v /opt/zwavejs:/usr/src/app/store \
zwavejs/zwave-js-ui:latest
Z-Wave JS UI (zwavejs2mqtt)
# docker-compose.yml
version: '3.8'
services:
zwavejs:
image: zwavejs/zwave-js-ui:latest
restart: unless-stopped
tty: true
stop_signal: SIGINT
devices:
- /dev/ttyUSB0:/dev/ttyUSB0
ports:
- "8091:8091" # Web UI
- "3000:3000" # WebSocket
volumes:
- ./zwavejs-store:/usr/src/app/store
environment:
- TZ=America/New_York
docker compose up -d
# Access UI at http://localhost:8091
Finding Your Z-Wave Controller
# List USB devices
ls -la /dev/ttyUSB* /dev/ttyACM*
# Check USB device details
dmesg | grep -i "z-wave\|aeotec\|zooz\|silicon"
# Identify device
udevadm info -a -n /dev/ttyUSB0 | grep -E "vendor|product|serial"
# Create persistent device path (udev rule)
cat > /etc/udev/rules.d/99-zwave.rules << 'EOF'
SUBSYSTEM=="tty", ATTRS{idVendor}=="0658", ATTRS{idProduct}=="0200", SYMLINK+="zwave"
EOF
sudo udevadm control --reload-rules
Z-Wave JS UI Configuration
Initial Setup
1. Open http://localhost:8091
2. Settings > Z-Wave:
- Serial Port: /dev/ttyUSB0
- Security Keys: Generate (S0, S2 Unauthenticated, S2 Authenticated, S2 Access Control)
3. Settings > MQTT (optional):
- Host: mqtt://localhost:1883
- Enable MQTT gateway
4. Click Save and restart
Security Keys
{
"S2_Unauthenticated": "auto-generated-32-byte-hex",
"S2_Authenticated": "auto-generated-32-byte-hex",
"S2_AccessControl": "auto-generated-32-byte-hex",
"S0_Legacy": "auto-generated-16-byte-hex"
}
Device Management
Inclusion (Adding Devices)
Via Z-Wave JS UI:
1. Click "Manage Nodes" > "Include"
2. Choose security level:
- S2 Access Control (locks, garage doors)
- S2 Authenticated (sensors, thermostats)
- S2 Unauthenticated (lights, switches)
- S0 Legacy (older devices)
- No Security (non-critical devices)
3. Put device in inclusion mode (usually triple-click button)
4. Confirm DSK if S2 inclusion
Exclusion (Removing Devices)
Via Z-Wave JS UI:
1. Click "Manage Nodes" > "Exclude"
2. Put device in exclusion mode (usually triple-click button)
3. Device will be removed from network
Node Management
| Action | Description |
|---|
| Heal Node | Rediscover routes for a specific node |
| Heal Network | Rediscover routes for entire network |
| Ping Node | Check if node is reachable |
| Interview Node | Re-query device capabilities |
| Refresh Values | Update all values from device |
| Remove Failed | Remove a dead/failed node |
| Replace Failed | Replace failed node with new hardware |
| Firmware Update | OTA update device firmware |
MQTT Integration
Topic Structure
# State topics
zwave/<node_name>/status # online/offline
zwave/<node_name>/<command_class>/<property>
# Set topics
zwave/<node_name>/<command_class>/<property>/set
# Examples
zwave/living_room_dimmer/38/currentValue # Current brightness
zwave/living_room_dimmer/38/targetValue/set # Set brightness
zwave/front_door_lock/98/currentMode # Lock state
zwave/thermostat/67/setpoint/1/set # Set temperature
Control Examples
# Turn on a switch
mosquitto_pub -t "zwave/office_light/37/targetValue/set" -m "true"
# Turn off
mosquitto_pub -t "zwave/office_light/37/targetValue/set" -m "false"
# Set dimmer to 75%
mosquitto_pub -t "zwave/living_room_dimmer/38/targetValue/set" -m "75"
# Lock a door
mosquitto_pub -t "zwave/front_door_lock/98/targetMode/set" -m "255"
# Unlock
mosquitto_pub -t "zwave/front_door_lock/98/targetMode/set" -m "0"
# Set thermostat
mosquitto_pub -t "zwave/thermostat/67/setpoint/1/set" -m "72"
# Set thermostat mode
mosquitto_pub -t "zwave/thermostat/64/mode/set" -m "1" # Heat
# Subscribe to all Z-Wave events
mosquitto_sub -t "zwave/#" -v
# Subscribe to specific sensor
mosquitto_sub -t "zwave/motion_sensor/#" -v
Z-Wave JS API (Programmatic)
const { Driver } = require("zwave-js");
// Initialize driver
const driver = new Driver("/dev/ttyUSB0", {
securityKeys: {
S2_Unauthenticated: Buffer.from("...", "hex"),
S2_Authenticated: Buffer.from("...", "hex"),
S2_AccessControl: Buffer.from("...", "hex"),
S0_Legacy: Buffer.from("...", "hex"),
},
});
driver.once("driver ready", () => {
// List all nodes
for (const [nodeId, node] of driver.controller.nodes) {
console.log(`Node ${nodeId}: ${node.deviceConfig?.description}`);
}
// Get specific node
const node = driver.controller.nodes.get(5);
// Get value
const temp = node.getValue({
commandClass: 49, // Sensor Multilevel
property: "Air temperature",
});
console.log(`Temperature: ${temp}°F`);
// Set value (turn on switch)
node.setValue(
{ commandClass: 37, property: "targetValue" },
true
);
});
driver.start();
Command Classes
Common Command Classes
| CC ID | Name | Description |
|---|
| 0x25 | Binary Switch | On/Off switches |
| 0x26 | Multilevel Switch | Dimmers, motor controls |
| 0x30 | Binary Sensor | Motion, door/window sensors |
| 0x31 | Multilevel Sensor | Temperature, humidity, light |
| 0x40 | Thermostat Mode | Heat, cool, auto, off |
| 0x43 | Thermostat Setpoint | Temperature setpoints |
| 0x62 | Door Lock | Smart locks |
| 0x63 | User Code | Lock PIN codes |
| 0x70 | Configuration | Device parameters |
| 0x71 | Notification | Alerts and events |
| 0x72 | Manufacturer Specific | Device identity |
| 0x80 | Battery | Battery level |
| 0x84 | Wake Up | Sleep/wake schedule |
| 0x86 | Version | Protocol/firmware version |
| 0x8E | Multi Channel Assoc. | Multi-endpoint association |
| 0x98 | Security S0 | Legacy security |
| 0x9F | Security S2 | Modern security |
Advanced Usage
Associations
# Direct association: device A controls device B without controller
Via Z-Wave JS UI:
Node > Associations tab
- Group 2 (usually "basic set")
- Add target node ID
- Commands go directly between devices
Device Parameters
# Configure device parameters
Via Z-Wave JS UI:
Node > Configuration tab
- Parameter 1: LED indicator mode
- Parameter 3: Auto-off timer
- Parameter 4: Power restore state
# Values are device-specific - check manufacturer docs
Network Optimization
# Heal network (rebuild routing tables)
# Via UI: Settings > Z-Wave > Heal Network
# Best done at night when devices are idle
# Priority Return Route
# Assigns optimal routes for time-critical devices
# Check network health
# Via UI: Network Map shows mesh topology
# Look for nodes with few connections (weak mesh)
Firmware Updates
Via Z-Wave JS UI:
1. Node > Firmware Update
2. Select firmware file (.hex, .otz, .ota, .gbl)
3. Target: usually 0 (main chip)
4. Click Begin Firmware Update
5. Wait for completion (can take 5-30 minutes)
Configuration
Z-Wave JS Server Settings
{
"port": "/dev/ttyUSB0",
"options": {
"logConfig": {
"enabled": true,
"level": "info",
"filename": "/var/log/zwave.log"
},
"storage": {
"cacheDir": "/opt/zwavejs/cache"
},
"preferences": {
"scales": {
"temperature": "°F"
}
}
}
}
Troubleshooting
| Issue | Solution |
|---|
| Controller not found | Check USB path, install drivers, try other port |
| Device won’t include | Exclude first, move closer, factory reset device |
| Device shows “dead” | Check battery/power, try ping, heal network |
| Slow response | Add repeaters, heal network, check routes |
| S2 inclusion fails | Verify DSK, check security keys configured |
| MQTT not publishing | Check MQTT broker connection, verify gateway mode |
| Device drops off network | Check for interference, add router devices |
| Wrong values reported | Re-interview node, check configuration params |
Diagnostic Commands
# Check Z-Wave JS UI logs
docker logs -f zwavejs
# Check controller info via UI
# Settings > Z-Wave > Controller Info
# Network statistics
# Shows dropped packets, routing failures
# Check USB device health
dmesg | tail -20
lsusb -v | grep -A 5 "Z-Wave\|0658"
# Z-Wave frequency by region
# US/Canada: 908.42 MHz
# Europe: 868.42 MHz
# Australia: 921.42 MHz
# Ensure controller matches your region