Awesome Open Source
Awesome Open Source

Z-Wave JS Server

Small server wrapper around Z-Wave JS to access it via a WebSocket.

Trying it out

These instructions are for development only. These CLIs will be available as zwave-server and zwave-client after installing the NPM package.

Start server

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.

NOTE: Unless specificed in the configuration file, the emitValueUpdateAfterSetValue configuration option will be set to true. This is recommended for multi-client setups and for cases where multiple applications are sharing access to the same driver, e.g. zwavejs2mqtt

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.

DNS-SD is enabled in the server by default. If you would like to disable it, add --disable-dns-sd.

Start client

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.

API

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;
  }
}

Client commands

Server level commands

Start listening to events

interface {
  messageId: string;
  command: "start_listening";
}

Set API schema version

[compatible with schema version: 0+]

interface {
  messageId: string;
  command: "set_api_schema";
  schemaVersion: number;
}

Driver level commands

Get the config of the driver

[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;
  }
}

Update the logging configuration

[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;
  }
}

Get the logging configuration

[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;
  }
}

Enable data usage statistics collection

[compatible with schema version: 4+]

interface {
  messageId: string;
  command: "driver.enable_statistics";
  applicationName: string;
  applicationVersion: string;
}

Disable data usage statistics collection

[compatible with schema version: 4+]

interface {
  messageId: string;
  command: "driver.disable_statistics";
}

Get whether statistics are enabled

[compatible with schema version: 4+]

interface {
  messageId: string;
  command: "driver.is_statistics_enabled";
}

Returns:

interface {
  statisticsEnabled: boolean;
}

Set preferred scales

[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"];
}

Start listening to logging events

[compatible with schema version: 4+]

Start receiving logs as events. Look at the logging event documentation for more information about the events. If filter is included, only logs that match the filter will be sent as events, the rest will be skipped.

interface {
  messageId: string;
  command: "driver.start_listening_logs";
  filter?: Partial<NodeLogContext| DriverLogContext | ControllerLogContext | SerialLogContext | ConfigLogContext>;
}

Stop listening to logging events

[compatible with schema version: 4+]

Stop receiving logs as events.

interface {
  messageId: string;
  command: "driver.stop_listening_logs";
}

Check for config updates

[compatible with schema version: 5+]

interface {
  messageId: string;
  command: "driver.check_for_config_updates";
}

Install config update

[compatible with schema version: 5+]

interface {
  messageId: string;
  command: "driver.install_config_update";
}

Enable Error Reporting

[compatible with schema version: 16+]

interface {
  messageId: string;
  command: "driver.enable_error_reporting";
}

Controller level commands

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 a QRProvisioningInformation 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 a QRProvisioningInformation 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.

Get controller state

[compatible with schema version: 14+]

interface {
  messageId: string;
  command: "controller.get_state";
}

Node level commands

Set value on a node

[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;
}

Refresh node info

[compatible with schema version: 0+]

interface {
  messageId: string;
  command: "node.refresh_info";
  nodeId: number;
  options?: RefreshInfoOptions;
}

Get defined Value IDs

[compatible with schema version: 0+]

interface {
  messageId: string;
  command: "node.get_defined_value_ids";
  nodeId: number;
}

Get value metadata

[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;
  };
}

Get value

[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;
  };
}

Get node state

[compatible with schema version: 14+]

interface {
  messageId: string;
  command: "node.get_state";
  nodeId: number;
}

Begin Firmware Update

[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;
  target?: number;
}

Abort Firmware Update

[compatible with schema version: 0+]

interface {
  messageId: string;
  command: "node.abort_firmware_update";
  nodeId: number;
}

Get Firmware Update Capabilities

[compatible with schema version: 0+]

interface {
  messageId: string;
  command: "node.get_firmware_update_capabilities";
  nodeId: number;
}

Get Firmware Update Capabilities Cached

[compatible with schema version: 21+]

interface {
  messageId: string;
  command: "node.get_firmware_update_capabilities_cached";
  nodeId: number;
}

Poll value

[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;
  };
}

Set raw configuration parameter value (Advanced)

[compatible with schema version: 1+]

interface {
  messageId: string;
  command: "node.set_raw_config_parameter_value";
  nodeId: number;
}

Refresh values

[compatible with schema version: 4+]

interface {
  messageId: string;
  command: "node.refresh_values";
  nodeId: number;
}

Interview command class

[compatible with schema version: 14+]

interface {
  messageId: string;
  nodeId: number;
  command: "node.interview_cc";
  commandClass: CommandClasses;
}

Refresh command class values

[compatible with schema version: 4+]

interface {
  messageId: string;
  command: "node.refresh_cc_values";
  nodeId: number;
  commandClass: CommandClasses;
}

Ping

[compatible with schema version: 5+]

interface {
  messageId: string;
  command: "node.ping";
  nodeId: number;
}

Check if node has security class

[compatible with schema version: 8+]

interface {
  messageId: string;
  command: "node.has_security_class";
  nodeId: number;
  securityClass: SecurityClass;
}

Return the highest security class the node has

[compatible with schema version: 8+]

interface {
  messageId: string;
  nodeId: number;
  command: "node.get_highest_security_class";
}

Test Powerlevel

[compatible with schema version: 13+]

This command emits events.

interface {
  messageId: string;
  nodeId: number;
  command: "node.test_powerlevel";
  testNodeId: number;
  powerlevel: Powerlevel;
  testFrameCount: number;
}

Check Lifeline Health

[compatible with schema version: 13+]

This command emits events.

interface {
  messageId: string;
  nodeId: number;
  command: "node.check_lifeline_health";
  rounds?: number;
}

Check Route Health

[compatible with schema version: 13+]

This command emits events.

interface {
  messageId: string;
  nodeId: number;
  command: "node.check_route_health";
  targetNodeId: number;
  rounds?: number;
}

Get endpoint count

[compatible with schema version: 14+]

interface {
  messageId: string;
  nodeId: number;
  command: "node.get_endpoint_count";
}

Set name

[compatible with schema version: 14+]

interface {
  messageId: string;
  nodeId: number;
  command: "node.set_name";
  updateCC?: boolean = true;
}

Set location

[compatible with schema version: 14+]

interface {
  messageId: string;
  nodeId: number;
  command: "node.set_location";
  updateCC?: boolean = true;
}

Set keep awake

[compatible with schema version: 14+]

interface {
  messageId: string;
  nodeId: number;
  command: "node.set_keep_awake";
}

Is Firmware Update In Progress

[compatible with schema version: 21+]

interface {
  messageId: string;
  nodeId: number;
  command: "node.is_firmware_update_in_progress";
}

Wait for wakeup

[compatible with schema version: 18+]

interface {
  messageId: string;
  nodeId: number;
  command: "node.wait_for_wakeup";
}

Endpoint level commands

Invoke a Command Classes API method

[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
}

Check whether a given Command Classes API is supported by the above method

[compatible with schema version: 7+]

interface {
  messageId: string;
  command: "endpoint.supports_cc_api";
  nodeId: number;
  endpoint?: number;
  commandClass: CommandClasses;
}

Multicasting

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;
}

Set value

[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;
}

Get endpoint count

[compatible with schema version: 5+]

interface {
  messageId: string;
  command: "<prefix>.get_endpoint_count"
}

Check if endpoint supports a Command Class

[compatible with schema version: 5+]

interface {
  messageId: string;
  command: "<prefix>.supports_cc"
  index: number
  commandClass: CommandClasses
}

Get Command Class version on an endpoint

[compatible with schema version: 5+]

interface {
  messageId: string;
  command: "<prefix>.get_cc_version"
  index: number
  commandClass: CommandClasses
}

Invoke a Command Class specific API

[compatible with schema version: 5+]

interface {
  messageId: string;
  command: "<prefix>.invoke_cc_api"
  index?: number;  // Endpoint index
  commandClass: CommandClasses;
  methodName: string;
  args: unknown[];
}

Check if a Command Class is supported by invoke_cc_api

[compatible with schema version: 5+]

interface {
  messageId: string;
  command: "<prefix>.get_cc_version"
  index?: number;  // Endpoint index
  commandClass: CommandClasses;
}

Get defined value IDs

[compatible with schema version: 11+]

interface {
  messageId: string;
  command: "<prefix>.get_defined_value_ids"
}

Utility commands

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.

Events

zwave-js Events

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
  }
}

zwave-js-server Node Events

test powerlevel progress

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;
  }
}

check lifeline health progress

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;
  }
}

check route health progress

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;
  }
}

zwave-js-server Driver Events

log config updated

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`
  }
}

logging

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[];
  }
}

zwave-js-server Controller Events

grant security classes

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;
  }
}

validate dsk and enter pin

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;
  }
}

inclusion aborted

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";
  }
}

nvm backup progress

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;
  }
}

nvm convert progress

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;
  }
}

nvm restore progress

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;
  }
}

Schema Version

In an attempt to keep compatibility between different server and client versions, we've introduced a (basic) API Schema Version.

  1. 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
    }
    
  2. 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.

  3. 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.

  4. 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.

  5. 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"}
    
  6. 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.

Errors

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" }

Authentication

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.

Related Awesome Lists
Top Programming Languages
Top Projects

Get A Weekly Email With Trending Projects For These Topics
No Spam. Unsubscribe easily at any time.
Js (1,074,349
Typescript (255,886
Server (66,097
Authentication (26,891
Websocket (13,926
Zwave (281