This is a node.js add-on which wraps the Open Z-Wave library to provide access to a Z-Wave network from JavaScript.
It is currently able to scan a Z-Wave network, report on connected devices, monitor the network for changes, and has rudimentary write support.
$ npm install openzwave
Start by loading the addon and creating a new instance, specifying a path to the USB device:
var OZW = require('openzwave');
var zwave = new OZW('/dev/ttyUSB0');
An optional object can be passed at creation time to alter the behavior of the ZWave module. The options currently supported and their defaults are:
var zwave = new OZW('/dev/ttyUSB0', {
logging: false, // enable logging to OZW_Log.txt
consoleoutput: false, // copy logging to the console
saveconfig: false, // write an XML network layout
driverattempts: 3, // try this many times before giving up
});
The rest of the API is split into Functions and Events. Messages from the
Z-Wave network are handled by EventEmitter
, and you will need to listen for
specific events to correctly map the network.
Connecting to the network:
zwave.connect(); // initialise and start a new driver.
zwave.disconnect(); // disconnect from the current connection
Modifying device state:
/*
* Set a multi-level device to the specified level (between 0-99).
*/
zwave.setLevel(nodeid, level);
/*
* Turn a binary switch on/off.
*/
zwave.switchOn(nodeid);
zwave.switchOff(nodeid);
/*
* Set arbitrary values.
*/
zwave.setValue(nodeid, commandclass, index, value);
Writing to device metadata (stored on the device itself):
zwave.setLocation(nodeid, location); // arbitrary location string
zwave.setName(nodeid, name); // arbitrary name string
Polling a device for changes (not all devices require this):
zwave.enablePoll(nodeid, commandclass);
zwave.disablePoll(nodeid, commandclass);
Reset the controller. Calling hardReset
will clear any associations, so use
carefully:
zwave.hardReset(); // destructive! will wipe out all known configuration
zwave.softReset(); // non-destructive, just resets the chip
The supported events are:
We have connected to an OpenZWave node.
The OpenZWave driver has initialised and scanning has started. Returns a
unique homeid
which identifies this particular network.
The OpenZWave driver failed to initialise.
A new node has been found on the network. At this point you can allocate resources to hold information about this node.
A new value has been discovered. Values are associated with a particular node, and are the parts of the device you can monitor or control.
Values are split into command classes. The classes currently supported and their unique identifiers are:
COMMAND_CLASS_SWITCH_BINARY
(37)COMMAND_CLASS_SWITCH_MULTILEVEL
(38)COMMAND_CLASS_VERSION
(134)
Binary switches can be controlled with .switchOn()
and .switchOff()
.
Multi-level devices can be set with .setLevel()
.
The version class is informational only and cannot be controlled.
The value
object differs between command classes, and contains all the useful
information about values stored for the particular class.
A value has changed. Use this to keep track of value state across the network. When values are first discovered, the module enables polling on those values so that we will receive change messages.
Prior to the 'node ready' event, there may be 'value changed' events even when no values were actually changed.
A value has been removed. Use the index to calculate the offset where a command class can contain multiple values.
A node is now ready for operation, and information about the node is available
in the nodeinfo
object:
nodeinfo.manufacturer
nodeinfo.product
nodeinfo.type
nodeinfo.loc
(location, renamed to avoidlocation
keyword).
The initial network scan has finished.
The test program below connects to a Z-Wave network, scans for all nodes and
values, and prints out information about the network. It will then continue to
scan for changes until the user hits ^C
.
/*
* OpenZWave test program.
*/
var OpenZWave = require('openzwave');
var zwave = new OpenZWave('/dev/ttyUSB0', {
saveconfig: true,
});
var nodes = [];
zwave.on('driver ready', function(homeid) {
console.log('scanning homeid=0x%s...', homeid.toString(16));
});
zwave.on('driver failed', function() {
console.log('failed to start driver');
zwave.disconnect();
process.exit();
});
zwave.on('node added', function(nodeid) {
nodes[nodeid] = {
manufacturer: '',
manufacturerid: '',
product: '',
producttype: '',
productid: '',
type: '',
name: '',
loc: '',
classes: {},
ready: false,
};
});
zwave.on('value added', function(nodeid, comclass, value) {
if (!nodes[nodeid]['classes'][comclass])
nodes[nodeid]['classes'][comclass] = {};
nodes[nodeid]['classes'][comclass][value.index] = value;
});
zwave.on('value changed', function(nodeid, comclass, value) {
if (nodes[nodeid]['ready']) {
console.log('node%d: changed: %d:%s:%s->%s', nodeid, comclass,
value['label'],
nodes[nodeid]['classes'][comclass][value.index]['value'],
value['value']);
}
nodes[nodeid]['classes'][comclass][value.index] = value;
});
zwave.on('value removed', function(nodeid, comclass, index) {
if (nodes[nodeid]['classes'][comclass] &&
nodes[nodeid]['classes'][comclass][index])
delete nodes[nodeid]['classes'][comclass][index];
});
zwave.on('node ready', function(nodeid, nodeinfo) {
nodes[nodeid]['manufacturer'] = nodeinfo.manufacturer;
nodes[nodeid]['manufacturerid'] = nodeinfo.manufacturerid;
nodes[nodeid]['product'] = nodeinfo.product;
nodes[nodeid]['producttype'] = nodeinfo.producttype;
nodes[nodeid]['productid'] = nodeinfo.productid;
nodes[nodeid]['type'] = nodeinfo.type;
nodes[nodeid]['name'] = nodeinfo.name;
nodes[nodeid]['loc'] = nodeinfo.loc;
nodes[nodeid]['ready'] = true;
console.log('node%d: %s, %s', nodeid,
nodeinfo.manufacturer ? nodeinfo.manufacturer
: 'id=' + nodeinfo.manufacturerid,
nodeinfo.product ? nodeinfo.product
: 'product=' + nodeinfo.productid +
', type=' + nodeinfo.producttype);
console.log('node%d: name="%s", type="%s", location="%s"', nodeid,
nodeinfo.name,
nodeinfo.type,
nodeinfo.loc);
for (comclass in nodes[nodeid]['classes']) {
switch (comclass) {
case 0x25: // COMMAND_CLASS_SWITCH_BINARY
case 0x26: // COMMAND_CLASS_SWITCH_MULTILEVEL
zwave.enablePoll(nodeid, comclass);
break;
}
var values = nodes[nodeid]['classes'][comclass];
console.log('node%d: class %d', nodeid, comclass);
for (idx in values)
console.log('node%d: %s=%s', nodeid, values[idx]['label'], values[idx]['value']);
}
});
zwave.on('notification', function(nodeid, notif) {
switch (notif) {
case 0:
console.log('node%d: message complete', nodeid);
break;
case 1:
console.log('node%d: timeout', nodeid);
break;
case 2:
console.log('node%d: nop', nodeid);
break;
case 3:
console.log('node%d: node awake', nodeid);
break;
case 4:
console.log('node%d: node sleep', nodeid);
break;
case 5:
console.log('node%d: node dead', nodeid);
break;
case 6:
console.log('node%d: node alive', nodeid);
break;
}
});
zwave.on('scan complete', function() {
console.log('scan complete, hit ^C to finish.');
});
zwave.connect();
process.on('SIGINT', function() {
console.log('disconnecting...');
zwave.disconnect();
process.exit();
});
Sample output from this program:
$ node test.js 2>/dev/null
scanning homeid=0x161db5f...
node1: Aeon Labs, Z-Stick S2
node1: name="", type="Static PC Controller", location=""
node1: class 32
node1: Basic=0
node11: Everspring, AD142 Plug-in Dimmer Module
node11: name="", type="Multilevel Power Switch", location=""
node11: class 32
node11: class 38
node11: Level=89
node11: Bright=undefined
node11: Dim=undefined
node11: Ignore Start Level=true
node11: Start Level=0
node11: class 39
node11: Switch All=3073
node11: class 115
node11: Powerlevel=3073
node11: Timeout=0
node11: Set Powerlevel=undefined
node11: Test Node=0
node11: Test Powerlevel=3072
node11: Frame Count=0
node11: Test=undefined
node11: Report=undefined
node11: Test Status=3072
node11: Acked Frames=0
node11: class 117
node11: Protection=3072
node11: class 134
node11: Library Version=4
node11: Protocol Version=2.64
node11: Application Version=1.02
node12: Wenzhou TKB Control System, product=0103, type=0101
node12: name="", type="Binary Power Switch", location=""
node12: class 32
node12: class 37
node12: Switch=true
node12: class 39
node12: Switch All=3161
node12: class 134
node12: Library Version=6
node12: Protocol Version=3.40
node12: Application Version=1.04
node13: Wenzhou TKB Control System, product=0103, type=0101
node13: name="", type="Binary Power Switch", location=""
node13: class 32
node13: class 37
node13: Switch=true
node13: class 39
node13: Switch All=3073
node13: class 134
node13: Library Version=6
node13: Protocol Version=3.40
node13: Application Version=1.04
node10: Popp / Duwi, ZW ESJ Blind Control
node10: name="", type="Multiposition Motor", location=""
node10: class 32
node10: class 37
node10: Switch=true
node10: class 38
node10: Level=99
node10: Bright=undefined
node10: Dim=undefined
node10: Ignore Start Level=true
node10: Start Level=0
node10: class 39
node10: Switch All=3073
node10: class 117
node10: Protection=3073
node10: class 134
node10: Library Version=6
node10: Protocol Version=2.51
node10: Application Version=1.00
node10: class 135
node10: Indicator=0
scan complete, hit ^C to finish.
^Cdisconnecting...
Remove 2>/dev/null
to get verbose output of all incoming notification types
and additional debug information.
The Open Z-Wave library that this module heavily relies upon is licensed under the GPLv3.
Everything else (all the bits that I have written) is under the vastly more sensible ISC license.