Small server wrapper around Z-Wave JS to access it via a WebSocket.
These instructions are for development only. These CLIs will be available as zwave-server
and zwave-client
after installing the NPM package.
ts-node src/bin/server.ts /dev/tty0
Opens server on ws://0.0.0.0:3000
.
You can specify a configuration file with --config
. This can be a JSON file or a JS file that exports the config. It needs to follow the Z-Wave JS config format.
You can specify a different port for the websocket server to listen on with --port
, as well as the interface to attach to using --host
, the default host is 0.0.0.0 i.e all interfaces.
If you don't have a USB stick, you can add --mock-driver
to use a fake stick.
NOTE: Unless specificed in the configuration file, the
emitValueUpdateAfterSetValue
configuration option will be set totrue
. This is recommended for multi-client setups and for cases where multiple applications are sharing access to the same driver, e.g. zwavejs2mqtt
Requires server to be running.
Default connects to ws://localhost:3000
:
ts-node src/bin/client.ts
To specify different host:
ts-node src/bin/client.ts ws://192.168.1.100:6000
To specify that it outputs each message on a single line so it can be replayed later:
ts-node src/bin/client.ts --dump
You can filter the output by a specific node ID:
ts-node src/bin/client.ts --node 52
To specify a schema version other than the latest (maxSchemaVersion
):
ts-node src/bin/client.ts --schemaVersion 0
All these options can be combined.
When a client connects, the server will send the version.
interface {
type: "version";
driverVersion: string;
serverVersion: string;
homeId: number;
}
To start receive the state and get events, the client needs to send the start_listening
command.
interface {
messageId: string;
command: "start_listening";
}
The server will respond with the current state and start sending events.
interface {
type: "result";
messageId: string; // maps the `start_listening` command
success: true,
result: {
state: {
controller: Partial<ZWaveController>;
nodes: Partial<ZWaveNode>[];
}
};
}
After that, the client will be notified of each state change that happens inside Z-Wave JS.
Event keys follow the names/types as used by Z-Wave JS.
interface {
type: "event",
event: {
source: "driver" | "controller" | "node";
event: string;
[key: string]: unknown;
}
}
interface {
messageId: string;
command: "start_listening";
}
[compatible with schema version: 0+]
interface {
messageId: string;
command: "set_api_schema";
schemaVersion: number;
}
[compatible with schema version: 4+]
interface {
messageId: string;
command: "driver.get_config";
}
Returns:
interface {
config: {
logConfig: {
enabled: boolean;
level: string | number; // schema versions >= 3 use string, <= 2 use number
logToFile: boolean;
filename: string;
forceConsole: boolean;
};
statisticsEnabled: boolean;
}
}
[compatible with schema version: 4+]
NOTE: You must provide at least one key/value pair as part of
config
interface {
messageId: string;
command: "driver.update_log_config";
config: {
enabled?: boolean;
level?: string | number;
logToFile?: boolean;
filename?: string;
forceConsole?: boolean;
}
}
[compatible with schema version: 4+]
interface {
messageId: string;
command: "driver.get_log_config";
}
Returns:
interface {
config: {
enabled: boolean;
level: string | number; // schema versions >= 3 use string, <= 2 use number
logToFile: boolean;
filename: string;
forceConsole: boolean;
}
}
[compatible with schema version: 4+]
interface {
messageId: string;
command: "driver.enable_statistics";
applicationName: string;
applicationVersion: string;
}
[compatible with schema version: 4+]
interface {
messageId: string;
command: "driver.disable_statistics";
}
[compatible with schema version: 4+]
interface {
messageId: string;
command: "driver.is_statistics_enabled";
}
Returns:
interface {
statisticsEnabled: boolean;
}
[compatible with schema version: 6+]
Set preferred sensor scales. The scales
argument has the same type as preferences.scales
in ZWaveOptions
interface {
messageId: string;
command: "driver.set_preferred_scales";
scales: ZWaveOptions["preferences"]["scales"];
}
[compatible with schema version: 4+]
Start receiving logs as events. Look at the logging
event documentation for more information about the events.
interface {
messageId: string;
command: "driver.start_listening_logs";
}
[compatible with schema version: 4+]
Stop receiving logs as events.
interface {
messageId: string;
command: "driver.stop_listening_logs";
}
[compatible with schema version: 5+]
interface {
messageId: string;
command: "driver.check_for_config_updates";
}
[compatible with schema version: 5+]
interface {
messageId: string;
command: "driver.install_config_update";
}
zwave-js-server
supports all of the controller methods listed in the Z-Wave JS documentation. zwave-js-server
uses snake casing for commands and prefixes every controller command with controller.
, so beginInclusion
is called using the controller.begin_inclusion
command.
NOTE: For the most part,
controller
commands have the same inputs as documented in the Z-Wave JS documentation. The exceptions are:
controller.begin_inclusion
: in addition to the input types that are documented, this command will also accept the QR code string directly and will convert the string to aQRProvisioningInformation
object automatically.controller.provision_smart_start_node
: in addition to the input types that are documented, this command will also accept the QR code string directly and will convert the string to aQRProvisioningInformation
object automatically.controller.backup_nvm_raw
: This command will return a base64 encoded string for the NVM data.controller.restore_nvm
: The NVM input should be a base64 encoded string.
[compatibile with schema version: 14+]
interface {
messageId: string;
command: "controller.get_state";
}
[compatible with schema version: 0+]
interface {
messageId: string;
command: "node.set_value";
nodeId: number;
valueId: {
commandClass: CommandClasses;
endpoint?: number;
property: string | number;
propertyKey?: string | number;
};
value: any;
options?: SetValueAPIOptions;
}
[compatible with schema version: 0+]
interface {
messageId: string;
command: "node.refresh_info";
nodeId: number;
options?: RefreshInfoOptions;
}
[compatible with schema version: 0+]
interface {
messageId: string;
command: "node.get_defined_value_ids";
nodeId: number;
}
[compatible with schema version: 0+]
interface {
messageId: string;
command: "node.get_value_metadata";
nodeId: number;
valueId: {
commandClass: CommandClasses;
endpoint?: number;
property: string | number;
propertyKey?: string | number;
};
}
[compatible with schema version: 14+]
interface {
messageId: string;
command: "node.get_value";
nodeId: number;
valueId: {
commandClass: CommandClasses;
endpoint?: number;
property: string | number;
propertyKey?: string | number;
};
}
[compatible with schema version: 14+]
interface {
messageId: string;
command: "node.get_state";
nodeId: number;
}
[compatible with schema version: 5+]
If firmwareFileFormat
is not provided, the format will be guessed based on the filename and file payload.
interface {
messageId: string;
command: "node.begin_firmware_update";
nodeId: number;
firmwareFilename: string;
firmwareFile: string; // use base64 encoding for the file
firmwareFileFormat?: FileFormat;
}
[compatible with schema version: 0+]
interface {
messageId: string;
command: "node.abort_firmware_update";
nodeId: number;
}
[compatible with schema version: 1+]
interface {
messageId: string;
command: "node.poll_value";
nodeId: number;
valueId: {
commandClass: CommandClasses;
endpoint?: number;
property: string | number;
propertyKey?: string | number;
};
}
[compatible with schema version: 1+]
interface {
messageId: string;
command: "node.set_raw_config_parameter_value";
nodeId: number;
}
[compatible with schema version: 4+]
interface {
messageId: string;
command: "node.refresh_values";
nodeId: number;
}
[compatible with schema version: 14+]
interface {
messageId: string;
nodeId: number;
command: "node.interview_cc";
commandClass: CommandClasses;
}
[compatible with schema version: 4+]
interface {
messageId: string;
command: "node.refresh_cc_values";
nodeId: number;
commandClass: CommandClasses;
}
[compatible with schema version: 5+]
interface {
messageId: string;
command: "node.ping";
nodeId: number;
}
[compatible with schema version: 8+]
interface {
messageId: string;
command: "node.has_security_class";
nodeId: number;
securityClass: SecurityClass;
}
[compatible with schema version: 8+]
interface {
messageId: string;
nodeId: number;
command: "node.get_highest_security_class";
}
[compatible with schema version: 13+]
This command emits events.
interface {
messageId: string;
nodeId: number;
command: "node.test_powerlevel";
testNodeId: number;
powerlevel: Powerlevel;
testFrameCount: number;
}
[compatible with schema version: 13+]
This command emits events.
interface {
messageId: string;
nodeId: number;
command: "node.check_lifeline_health";
rounds?: number;
}
[compatible with schema version: 13+]
This command emits events.
interface {
messageId: string;
nodeId: number;
command: "node.check_route_health";
targetNodeId: number;
rounds?: number;
}
[compatible with schema version: 14+]
interface {
messageId: string;
nodeId: number;
command: "node.get_endpoint_count";
}
[compatible with schema version: 14+]
interface {
messageId: string;
nodeId: number;
command: "node.set_name";
updateCC?: boolean = true;
}
[compatible with schema version: 14+]
interface {
messageId: string;
nodeId: number;
command: "node.set_location";
updateCC?: boolean = true;
}
[compatible with schema version: 14+]
interface {
messageId: string;
nodeId: number;
command: "node.set_keep_awake";
}
[compatible with schema version: 7+]
You can find all of the CC API methods in the Z-Wave JS docs.
Example: Invoking UserCodeCC.set
Send the following JSON to the server to invoke UserCodeCC.set(1, UserIDStatus.Enabled, "1234")
:
{
"messageId": "invoke-usercode-cc-set",
"command": "endpoint.invoke_cc_api",
"nodeId": 2,
"endpoint": 1,
"commandClass": 99, // commandClass = CommandClasses["User Code"]
"methodName": "set",
"args": [
1, // userId = 1
1, // userIdStatus = UserIDStatus.Enabled
"1234" // userCode = "1234"
]
}
interface {
messageId: string;
command: "endpoint.invoke_cc_api";
nodeId: number;
endpoint?: number;
commandClass: CommandClasses;
methodName: string;
args: unknown[];
}
For Buffer
type arguments, use the following JSON format to represent the argument:
{
"type": "Buffer",
"data": [] // array of numbers
}
[compatible with schema version: 7+]
interface {
messageId: string;
command: "endpoint.supports_cc_api";
nodeId: number;
endpoint?: number;
commandClass: CommandClasses;
}
There are several commands available that can be multicast to multiple nodes simultaneously. If you would like to broadcast to all nodes, use the broadcast_node
prefix for the following commands. If you would like to multicast to a subset of nodes, use the multicast_group
prefix for the following commands, adding a nodeIDs
list as an input parameter:
interface IncomingCommandMulticastGroupBase extends IncomingCommandBase {
nodeIDs: number[];
}
As an example, here's how you would call the set_value
command for a multicast group (note the extra nodeIDs
input parameter):
interface {
messageId: string;
command: "multicast_group.set_value";
nodeIDs: number[];
valueId: {
commandClass: CommandClasses;
endpoint?: number;
property: string | number;
propertyKey?: string | number;
};
value: any;
options?: SetValueAPIOptions;
}
[compatible with schema version: 5+]
interface {
messageId: string;
command: "<prefix>.set_value";
valueId: {
commandClass: CommandClasses;
endpoint?: number;
property: string | number;
propertyKey?: string | number;
};
value: any;
options?: SetValueAPIOptions;
}
[compatible with schema version: 5+]
interface {
messageId: string;
command: "<prefix>.get_endpoint_count"
}
[compatible with schema version: 5+]
interface {
messageId: string;
command: "<prefix>.supports_cc"
index: number
commandClass: CommandClasses
}
[compatible with schema version: 5+]
interface {
messageId: string;
command: "<prefix>.get_cc_version"
index: number
commandClass: CommandClasses
}
[compatible with schema version: 5+]
interface {
messageId: string;
command: "<prefix>.invoke_cc_api"
index?: number; // Endpoint index
commandClass: CommandClasses;
methodName: string;
args: unknown[];
}
[compatible with schema version: 5+]
interface {
messageId: string;
command: "<prefix>.get_cc_version"
index?: number; // Endpoint index
commandClass: CommandClasses;
}
[compatible with schema version: 11+]
interface {
messageId: string;
command: "<prefix>.get_defined_value_ids"
}
zwave-js-server
supports all of the utility methods listed in the Z-Wave JS documentation. zwave-js-server
uses snake casing for commands and prefixes every controller command with utils.
, so parseQRCodeString
is called using the utils.parse_qr_code_string
command.
All zwave-js
events as documented are forwarded on to clients that have sent the start_listening
command.
interface {
type: "event";
event: {
source: "driver" | "controller" | "node";
event: string;
... // Additional parameters dependent on the event, see zwave-js docs for more details
}
}
This event is sent after a node.test_powerlevel
command is issued and contains test results from the driver. See the zwave-js
docs on this command for more information.
interface {
type: "event";
event: {
source: "node";
event: "test powerlevel progress";
nodeId: number;
acknowledged: number;
total: number;
}
}
This event is sent after a node.check_lifeline_health
command is issued and contains test results from the driver. See the zwave-js
docs on this command for more information.
interface {
type: "event";
event: {
source: "node";
event: "check lifeline health progress";
nodeId: number;
round: number;
totalRounds: number;
lastRating: number;
}
}
This event is sent after a node.check_route_health
command is issued and contains test results from the driver. See the zwave-js
docs on this command for more information.
interface {
type: "event";
event: {
source: "node";
event: "check route health progress";
nodeId: number;
rounds: number;
totalRounds: number;
lastRating: number;
}
}
This event is sent whenever a client issues the driver.update_log_config
command with the updated log config.
interface {
type: "event";
event: {
source: "driver";
event: "log config updated";
config: Partial<LogConfig>; // Includes everything but `transports`
}
}
This event is sent whenever zwave-js
logs a statement. Clients will only receive these events when they have issued the driver.start_listening_logs
command.
interface {
type: "event";
event: {
source: "driver";
event: "logging";
formattedMessage: string;
direction: string;
primaryTags?: string;
secondaryTags?: string;
secondaryTagPadding?: number;
multiline?: boolean;
timestamp?: string;
label?: string;
message: string | string[];
}
}
This event is sent as part of the node inclusion process (including when replacing a failed node). The event indicates to the client that the user needs to choose which security classes to grant the node.
interface {
type: "event";
event: {
source: "controller";
event: "grant security classes";
requested: InclusionGrant;
}
}
This event is sent as part of the node inclusion process (including when replacing a failed node). The event indicates to the client that the user needs to confirm the provided DSK is valid and enter the PIN from the device.
interface {
type: "event";
event: {
source: "controller";
event: "validate dsk and enter pin";
dsk: string;
}
}
This event is sent as part of the node inclusion process (including when replacing a failed node). The event indicates to the client that the controller aborted the security bootstrapping process (this will occur after inclusion has already been successful). The logs may have more details on why this security bootstrapping process was aborted.
interface {
type: "event";
event: {
source: "controller";
event: "inclusion aborted";
}
}
This event is sent on progress updates to the NVM backup process when the controller.backup_nvm_raw
command is issued by a client to the server and a backup is in progress.
interface {
type: "event";
event: {
source: "controller";
event: "nvm backup progress";
bytesRead: number;
total: number;
}
}
This event is sent on progress updates to the NVM conversion process when the controller.restore_nvm
command is issued by a client to the server and the NVM file that was passed in is being converted to the right format.
interface {
type: "event";
event: {
source: "controller";
event: "nvm backup progress";
bytesRead: number;
total: number;
}
}
This event is sent on progress updates to the NVM restoration process when the controller.restore_nvm
command is issued by a client to the server and the NVM data is being restored to the controller.
interface {
type: "event";
event: {
source: "controller";
event: "nvm backup progress";
bytesWritten: number;
total: number;
}
}
In an attempt to keep compatibility between different server and client versions, we've introduced a (basic) API Schema Version.
-
client connects --> server sends back version info including the schema versions it can handle:
{ "type": "version", "driverVersion": "6.5.0", "serverVersion": "1.0.0", "homeId": 3967882672, "minSchemaVersion": 0, "maxSchemaVersion": 1 }
-
Client decides what to do based on supported schema version. For example drop connection if the supported server schema is too old or just handle the supported schema itself. For example most/all basic commands will just work but relatively new commands won't and the client decides to only not handle the stuff in the upgraded schema.
-
Client needs to tell the server what schema it wants to use. This is done with the "set_api_schema" command:
{ "command": "set_api_schema", "messageId": 1, "schemaVersion": 1 }
From this moment the server knows how to treat commands to/from this client. The server can handle multiple clients with different schema versions.
-
By default the server will use the minimum schema it supports (which is 0 at this time) if the
set_api_schema
command is omitted. -
If the client sends a schema version which is out of range, this will produce an error to the client and in the server's log:
{ "command": "set_api_schema", "messageId": 1, "schemaVersion": 3 } {"type":"result","success":false,"messageId":1,"errorCode":"schema_incompatible"}
-
When we make breaking changes in the api, we bump the schema version. When adding new commands/features, we also bump the api schema and note in both code comments and documentation to which schema version that feature is compatible with.
If a command results in an error, the following response is returned:
{
"type": "result",
"success": false,
"messageId": 1,
"errorCode": "schema_incompatible"
}
The following error codes exist:
code | description |
---|---|
unknown_command | Unknown command |
node_not_found | Node not found |
schema_incompatible | Incompatible Schema |
zwave_error | Error from Z-Wave JS |
unknown_error | Unknown exception |
In the case of zwave_error
, the extra keys zwaveErrorCode
and zwaveErrorMessage
will be added.
{ "type": "result", "success": false, "messageId": 1, "errorCode": "zwave_error", "zwaveErrorCode": 18, "zwaveErrorMessage": "The message cannot be sent because node 61 is dead" }
Z-Wave JS Server does not handle authentication and allows all connections to the websocket API. If you want to add authentication, add authentication middleware to your Express instance or run NGINX in front of Express instance.