Is Z-Wave JS Down? Real-Time Status & Outage Checker
Is Z-Wave JS Down? Real-Time Status & Outage Checker
Z-Wave JS is an open-source Z-Wave driver implementation with 1,500+ GitHub stars, originally created by Dominic Griesel and maintained by the Open Z-Wave community. It powers Z-Wave device control in Home Assistant through the Z-Wave JS UI add-on, supporting Z-Wave, Z-Wave Plus, and Z-Wave Long Range protocols. The project exposes a WebSocket server API, supports over-the-air firmware updates, and ships a web-based UI for device inclusion, exclusion, and management. It is the backbone for home automation enthusiasts running door locks, thermostats, motion sensors, and other Z-Wave smart home hardware.
A Z-Wave JS outage silently breaks entire categories of home automation: locks stop responding to schedules, thermostats ignore setpoints, and Home Assistant automations that depend on sensor state changes halt without obvious error messages. Because the failure surface spans USB hardware, Linux device nodes, a WebSocket process, and the Z-Wave radio mesh itself, diagnosing the root cause requires checking each layer independently.
Quick Status Check
#!/bin/bash
# Z-Wave JS health check
set -euo pipefail
ZWAVEJS_HOST="${ZWAVEJS_HOST:-localhost}"
ZWAVEJS_PORT="${ZWAVEJS_PORT:-3000}"
USB_PATHS=("/dev/ttyACM0" "/dev/ttyUSB0" "/dev/ttyAMA0")
echo "=== Z-Wave JS Status Check ==="
# 1. Check USB/serial controller presence
echo -n "Z-Wave USB controller: "
FOUND_USB=""
for path in "${USB_PATHS[@]}"; do
if [ -e "$path" ]; then
FOUND_USB="$path"
echo "DETECTED ($path)"
break
fi
done
[ -z "$FOUND_USB" ] && echo "NOT FOUND — check USB connection and udev rules"
# 2. Check WebSocket/HTTP port reachability
echo -n "Z-Wave JS HTTP port $ZWAVEJS_PORT: "
if nc -z -w3 "$ZWAVEJS_HOST" "$ZWAVEJS_PORT" 2>/dev/null; then
echo "OPEN"
else
echo "CLOSED — service may be down"
fi
# 3. Check HTTP health endpoint
echo -n "Z-Wave JS health endpoint: "
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
--connect-timeout 5 \
"http://${ZWAVEJS_HOST}:${ZWAVEJS_PORT}/health" 2>/dev/null || echo "000")
echo "HTTP $HTTP_STATUS"
# 4. Check Docker container if applicable
echo -n "Z-Wave JS Docker container: "
if command -v docker &>/dev/null; then
CONTAINER=$(docker ps --filter "name=zwave" --format "{{.Names}}" 2>/dev/null | head -1)
[ -n "$CONTAINER" ] && echo "RUNNING ($CONTAINER)" || echo "NOT RUNNING"
else
echo "Docker not available — checking process"
pgrep -f "zwave-js\|zwavejs2mqtt\|zwave-js-ui" &>/dev/null \
&& echo " Process: RUNNING" || echo " Process: NOT FOUND"
fi
# 5. Check MQTT bridge port if enabled
echo -n "MQTT bridge port 1883: "
nc -z -w2 "$ZWAVEJS_HOST" 1883 2>/dev/null \
&& echo "OPEN" || echo "CLOSED (MQTT bridge may be disabled)"
echo "=== Check complete ==="
Python Health Check
#!/usr/bin/env python3
"""
Z-Wave JS health check
Checks HTTP health endpoint, WebSocket port, controller state via WS API,
node count, and failed node detection.
"""
import asyncio
import json
import os
import socket
import sys
import time
import urllib.request
import urllib.error
HOST = os.environ.get("ZWAVEJS_HOST", "localhost")
HTTP_PORT = int(os.environ.get("ZWAVEJS_PORT", "3000"))
WS_PORT = HTTP_PORT
TIMEOUT = 10
results = []
def log(label: str, status: str, detail: str = "") -> None:
symbol = "OK" if status == "ok" else "FAIL"
line = f"[{symbol}] {label}"
if detail:
line += f": {detail}"
print(line)
results.append({"label": label, "status": status, "detail": detail})
def check_http_health() -> None:
url = f"http://{HOST}:{HTTP_PORT}/health"
try:
req = urllib.request.Request(url, headers={"Accept": "application/json"})
with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
body = resp.read().decode()
data = json.loads(body) if body else {}
log("HTTP health endpoint", "ok", f"HTTP 200, body={body[:80]}")
except urllib.error.HTTPError as e:
log("HTTP health endpoint", "fail", f"HTTP {e.code}")
except Exception as e:
log("HTTP health endpoint", "fail", str(e))
def check_ws_port() -> None:
try:
with socket.create_connection((HOST, WS_PORT), timeout=TIMEOUT):
log("WebSocket port", "ok", f"{HOST}:{WS_PORT} reachable")
except Exception as e:
log("WebSocket port", "fail", f"{HOST}:{WS_PORT} — {e}")
async def check_ws_controller() -> None:
"""Connect via raw WebSocket and query controller state."""
import websockets # type: ignore
uri = f"ws://{HOST}:{WS_PORT}"
try:
async with websockets.connect(uri, open_timeout=TIMEOUT) as ws:
# Read server hello
hello = await asyncio.wait_for(ws.recv(), timeout=TIMEOUT)
hello_data = json.loads(hello)
schema = hello_data.get("serverVersion", "unknown")
log("WebSocket handshake", "ok", f"serverVersion={schema}")
# Query controller state
cmd = json.dumps({"messageId": "1", "command": "controller.getState"})
await ws.send(cmd)
resp_raw = await asyncio.wait_for(ws.recv(), timeout=TIMEOUT)
resp = json.loads(resp_raw)
if resp.get("success"):
state = resp.get("result", {})
nodes = state.get("nodes", [])
node_count = len(nodes)
failed = [n for n in nodes if n.get("status") == 6] # status 6 = Dead
log("Z-Wave node count", "ok" if node_count > 0 else "fail",
f"{node_count} nodes in mesh")
if failed:
log("Failed nodes", "fail",
f"{len(failed)} dead node(s): IDs {[n.get('nodeId') for n in failed]}")
else:
log("Failed nodes", "ok", "No dead nodes detected")
else:
log("Controller state query", "fail",
resp.get("errorCode", "unknown error"))
except ImportError:
log("WebSocket controller check", "fail",
"websockets package not installed — run: pip install websockets")
except Exception as e:
log("WebSocket controller check", "fail", str(e))
def main() -> None:
print(f"=== Z-Wave JS Health Check — {HOST}:{HTTP_PORT} ===\n")
t0 = time.time()
check_ws_port()
check_http_health()
asyncio.run(check_ws_controller())
elapsed = time.time() - t0
failures = [r for r in results if r["status"] != "ok"]
print(f"\n--- Summary ({elapsed:.1f}s) ---")
print(f"Checks passed: {len(results) - len(failures)}/{len(results)}")
if failures:
print("Failures:")
for f in failures:
print(f" - {f['label']}: {f['detail']}")
sys.exit(1)
else:
print("All checks passed.")
if __name__ == "__main__":
main()
Common Z-Wave JS Outage Causes
| Symptom | Likely Cause | Resolution |
|---|---|---|
| Home Assistant shows "Z-Wave JS unavailable" | USB stick not detected after host reboot; udev rules missing or stick physically unplugged | Check /dev/ttyACM0 exists; add udev rule to assign a stable symlink; replug USB stick |
| New device cannot be added to the network | Inclusion mode failing; interference or S2 security handshake rejected | Move device closer to controller during inclusion; check S2 PIN on device label; try S0 or no security |
| Battery-powered node shows "Dead" | Device battery exhausted or out of radio range; no route through mesh | Replace battery; add a powered Z-Wave Plus device as repeater near the dead node |
| WebSocket connection drops; Home Assistant loses all Z-Wave control | Z-Wave JS UI process crashed or OOM-killed | Check container/process logs; increase memory limit; set restart policy to always |
| Controller firmware update stuck in loop | Partial firmware flash interrupted; controller in bootloader mode | Power-cycle USB stick; use Z-Wave JS UI recovery mode; reflash controller firmware |
| New S2 device pairs but commands not accepted | S2 security handshake mismatch; DSK entered incorrectly | Exclude device, re-include with correct 5-digit PIN from device label; check Z-Wave JS logs for security errors |
Architecture Overview
| Component | Function | Failure Impact |
|---|---|---|
| Z-Wave USB Controller | Radio transceiver; sends/receives Z-Wave frames over 908 MHz | Total loss of Z-Wave communication; all nodes unreachable |
Serial Port (/dev/ttyACM0) | Linux device node exposing USB stick to userspace | Z-Wave JS cannot open controller; startup fails |
| Z-Wave JS Driver Process | Parses Z-Wave protocol frames; manages node cache and routing tables | All node state goes stale; Home Assistant entities become unavailable |
| WebSocket Server (port 3000) | Exposes Z-Wave JS API to Home Assistant integration and UI | Home Assistant loses real-time control and state updates |
| Z-Wave JS UI (Web UI) | Device management, inclusion/exclusion, OTA updates, network map | Manual device management unavailable; automations unaffected if WS server is still up |
| MQTT Bridge (optional) | Publishes Z-Wave node state to MQTT broker for other integrations | MQTT consumers (Node-RED, custom scripts) lose Z-Wave state; HomeAssistant via WS unaffected |
Uptime History
| Date | Incident Type | Duration | Impact |
|---|---|---|---|
| 2025-07-14 | USB controller disconnect after host kernel update changed ttyACM ordering | 3 hours | All Z-Wave nodes unreachable; Home Assistant reported integration unavailable |
| 2025-10-03 | Z-Wave JS process OOM-killed on 512 MB device after large network cache growth | 45 minutes | WebSocket server down; all Z-Wave automations failed silently |
| 2025-12-19 | S2 security regression in driver version caused new device inclusions to fail | 6 hours | New devices could not be added to mesh; existing nodes unaffected |
| 2026-02-08 | MQTT bridge disconnected from broker after broker TLS certificate renewal | 2 hours | MQTT consumers lost Z-Wave state; WebSocket-based control remained functional |
Monitor Z-Wave JS Automatically
Z-Wave JS failures are especially hard to detect because Home Assistant may show stale entity states rather than explicit errors — your lock appears "unlocked" in the UI while the Z-Wave network is completely down. External monitoring catches this where internal dashboards cannot. ezmon.com monitors your Z-Wave JS endpoints from multiple external probes and alerts your team via Slack, PagerDuty, or SMS the moment the WebSocket port stops accepting connections or the health endpoint returns a non-200 response.