Z-Wave Command Object

The st.zwave.Command object is the base type inherited by all Z-Wave commands exchanged by Z-Wave drivers. st.zwave.Command provides serialization and deserialization facilities, and exposes both an opaque payload encoded to the Z-Wave specification and an abstracted table of decoded arguments. Driver code is intended to interact with decoded arguments, both inspecting received commands based upon these arguments, and constructing commands from them. Security/integrity and multichannel encapsulation parameters are also exposed.

Command Versioning

The Z-Wave command library is organized into Lua modules according to named command classes. These modules may be accessed with the require directive:

--- @type st.zwave.CommandClass.Basic
local Basic = require "st.zwave.CommandClass.Basic"

Command class modules returned by the require statement are callable to construct versioned command class instances that expose commands at the desired version. This is the typical use case. Construction of a versioned command class instance and command are as follows:

--- @type st.zwave.CommandClass.Basic
local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 })
local get = Basic:Get({ })

Each construction of a versioned command class object produces a distinct instance. Because of this, different versions may be used in different portions of a driver as needed. Multiple versions may also be constructed and accessed within a single code path:

--- @type st.zwave.CommandClass.SwitchMultilevel
local SwitchMultilevelV1 = (require "st.zwave.CommandClass.SwitchMultilevel")({ version = 1 })
--- @type st.zwave.CommandClass.SwitchMultilevel
local SwitchMultilevelV2 = (require "st.zwave.CommandClass.SwitchMultilevel")({ version = 2 })

local setv1 = SwitchMultilevelV1:Set({ value = 1 })
local setv2 = SwitchMultilevelV2:Set({ value = 1, duration = 2 })

Command Construction

The command library provides command construction from both tables of named arguments (serialization) and opaque payloads encoded to the Z-Wave specification (deserialization). The former is the majority use case for driver code. The latter facility is used by the framework to transform commands received from devices into command objects for receipt by drivers.

Construction from named arguments is as follows:

--- @type st.zwave.CommandClass.Basic
local Basic = (require "st.zwave.CommandClass.Basic")({ version = 2 })
local set = Basic:Set({ value = 1 })

By default, the library requires all command arguments to be explicitly passed to constructors. In this default configuration, missing arguments cause an assert. However, if desired, the library can instead populate zero-ish defaults (0 for numbers, false for booleans, zero-length strings for string types, etc.). To access this functionality, the strict flag must be set to false:

--- @type st.zwave.CommandClass.Basic
local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1, strict = false })
-- Normally, this would assert.  But with strict == false, the library will
-- populate this set with a default value == 0.
local set = Basic:Set({})

Command Encapsulation

The Edge Driver framework largely abstracts Z-Wave command encapsulation, primarily delegating encode and decode of encapsulation to other parts of the stack. However, multichannel encapsulation parameters and security/integrity encapsulation designations (CRC16, S0, etc.) are visible within command objects and settable if needed.

The following security/integrity encapsulation options are exposed:

  • AUTO

  • NONE

  • CRC16

  • S0

  • S2_UNAUTH

  • S2_AUTH

  • S2_ACCESS_CONTROL

AUTO security encapsulation is the default setting for constructed messages, and is almost always the right choice for outgoing traffic. This directs other layers of the stack to encapsulate with the highest security level available.

Overriding security encapsulation is highly discouraged. If a specific device behavior requires a different setting, this may be specified in the command instructor.

Encapsulation parameters are specified in command constructors as follows:

--- @type st.zwave
local zw = require "st.zwave"
--- @type st.zwave.CommandClass.Basic
local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 })
local set = Basic:Set({ value = 1 }, { encap = zw.ENCAP.AUTO, src_channel = 0, dst_channels = { 1, 2 } })

These parameters appear within both incoming and outgoing commands at the top-level fields encap, src_channel, and dst_channels. An example usage follows:

--- @type st.zwave.CommandClass.Basic
local Basic = (require "st.zwave.CommandClass.Basic")({ version = 1 })

--- @param device st.zwave.Device
local function multichannel_sender(device)
  -- Issue a basic set to the device's channel 2 destination endpoint.
  local set = Basic:Set({ value = 1 }, { dst_channels = { 2 } })
  device:send(set)
  -- Issue a basic get to the device's channel 2 destination endpoint.
  local get = Basic:Get({ value = 1 }, { dst_channels = { 2 } })
  device:send(get)
end

--- @param driver st.zwave.Driver
--- @param device st.zwave.Device
--- @param cmd st.zwave.CommandClass.Basic.Report
local function multichannel_receiver(driver, device, cmd)
  if cmd.src_channel == 1 then
    -- Do something with this report that originated from the device's
    -- source channel 1 endpoint.
  elseif cmd.src_channel == 2 then
    -- Do something else with this report that originated from the
    -- device's source channel 2 endpoint.
  end
end

Command Receipt

Commands received by drivers are dynamically versioned. This is to accommodate the fact that drivers’ devices may issue commands at any command class version. While it is possible to interrogate devices to learn their supported versions, the framework does not use this facility.

Instead, the framework attempts to parse commands from the framework’s highest supported version down to the lowest supported version. The library infers version based upon the highest version for which parse succeeds.

Within a driver, further version-specific branching can occur:

--- @param driver st.zwave.Driver
--- @param device st.zwave.Device
--- @param report st.zwave.CommandClass.Basic.Report
local function basic_report_handler(driver, device, cmd)
  if cmd.version == 1 then
    -- do something with version 1
  elseif cmd.version == 2 then
    -- do something else with version 2
  end
end

To ensure backward and forward compatibility, commands received from devices enclose the union of all possible fields that may be present at a command version and its predecessors. For instance, st.zwave.CommandClass.Basic.ReportV1 has a single field value. st.zwave.CommandClass.Basic.ReportV2 renames this field current_value, and adds target_value and duration.

A version 2 basic report received from a device will be passed to drivers with args containing the union of all fields from st.zwave.CommandClass.Basic.ReportV2Args and st.zwave.CommandClass.Basic.ReportV1Args, where field lua:attr:value <st.zwave.CommandClass.Basic.ReportV1Args.value> will shadow the equivalent v2 field current_value.

Command Parse Failures

Receiving code in the framework must accommodate both commands known to the library and those that are unrecognized. Below are some examples of commands that may be unrecognized:

  • Manufacturer-proprietary commands outside of the Z-Wave specification

  • Commands falling within the Z-Wave specification, but that are not explicitly supported by the library

  • New commands introduced after development of the library

  • Commands that are malformed, perhaps due to device firmware bugs or deviations from the Z-Wave spec

In all of these cases, command parsing will fail. However, driver code must still have an opportunity to handle such situations. In these cases, the framework will pass a partially constructed command object with args and version nil, but the raw payload intact and err enclosed to provide some error context.

For example, the following command is from an unrecognized command class:

> local zw = require "st.zwave"
> local cmd = zw.Command(0xFFFF, 0x01, "\x0A\x0B\x0C")
> print(cmd)
{cmd_class=65535, cmd_id=1, dst_channels={}, encap="AUTO", err="unsupported command class", payload="\x0A\x0B\x0C", src_channel=0}

This is a known command, but with a malformed payload:

> local zw = require "st.zwave"
> local cc = require "st.zwave.CommandClass"
> local Basic = require "st.zwave.CommandClass.Basic"
> local set = zw.Command(cc.BASIC, Basic.SET, "")
> print(set)
{cmd_class="BASIC", cmd_id="SET", dst_channels={}, encap="AUTO", err="...st/buf.lua:469: buffer too short", payload="", src_channel=0}

When needed, driver code can be written to accomodate these situations, either as exceptional cases or as expected operation due to some device-specific behavior.

Command Stringification

Z-Wave commands constructed by the library include a __tostring metatable method, and so may be passed through the tostring function and print to provide useful string-formatted output:

> Notification = require ("st.zwave.CommandClass.Notification")({ version = 3})
> report = Notification:Report({ notification_type = Notification.notification_type.SMOKE, event = Notification.event.smoke.DETECTED })
> print(report)
{args={event="DETECTED", event_parameter="", notification_status="OFF", notification_type="SMOKE", v1_alarm_level=0, v1_alarm_type=0}, cmd_class="NOTIFICATION", cmd_id="REPORT", dst_channels={}, encap="AUTO", payload="\x00\x00\x00\x00\x01\x02\x00", src_channel=0, version=3}

The st.zwave.Command __tostring metatable method always attempts to perform string conversion for known constants to their associated monikers.

Class Documentation

class st.zwave.Command
cmd_class: number

numerical Z-Wave command class designation

cmd_id: number

numerical Z-Wave command ID

version: number

numerical Z-Wave command version

args: table

command-specific arguments

payload: str

Z-Wave command payload

encap: number

Z-Wave security / integrity encapsulation, see st.zwave.ENCAP

src_channel: number

multichannel encapsulation source

dst_channels: table

multichannel encapsulation destinations

reflect()

Reflect all constants within command self into stringified monikers.

Returns

table representation of self command with all constants reflected to strings

Return type

table

pretty_print()

__tostring helper for st.zwave.Command objects.

Returns

stringified cmd self

Return type

str

equals(command)

Compare two commands for equality.

Parameters

command (st.zwave.Command) – command for comparison with self

Returns

true if commands’ data are equal

Return type

boolean

build_test_tx(number)

Build a version of this command as if it is being sent to the device

runner/src/envlib/socket.lua st_zwave_socket:send.

Parameters

number (device_id) – test device id

Returns

list of Z-Wave command positional parameters as are passed to

Return type

table

class st.zwave.ENCAP
AUTO: number
NONE: number
CRC16: number
S0: number
S2_UNAUTH: number
S2_AUTH: number
S2_ACCESS_CONTROL: number
UNKNOWN: number