diff --git a/10-zwave.html b/10-zwave.html
index 11bf3bc..9b672d0 100644
--- a/10-zwave.html
+++ b/10-zwave.html
@@ -32,135 +32,86 @@
-
-
-
diff --git a/10-zwave.js b/10-zwave.js
index d30d39d..d2aa1e2 100644
--- a/10-zwave.js
+++ b/10-zwave.js
@@ -18,67 +18,90 @@
*/
module.exports = function(RED) {
+
console.log("loading openzwave for node-red");
- var OZW = require('openzwave');
+
+ var OpenZWave = require('openzwave');
function ZWaveController(n) {
RED.nodes.createNode(this,n);
+ this.name = n.port;
this.port = n.port;
this.driverattempts = n.driverattempts;
this.pollinterval = n.pollinterval;
- this.zwave = new OZW(this.port, {
- logging: false, // enable logging to OZW_Log.txt
- consoleoutput: false, // copy logging to the console
+
+ // initialize OpenZWave
+ this.zwave = new OpenZWave(this.port, {
+ logging: false, // enable logging to OpenZWave_Log.txt
+ consoleoutput: true, // copy logging to the console
saveconfig: true, // write an XML network layout
driverattempts: this.driverattempts, // try this many times before giving up
pollinterval: this.pollinterval, // interval between polls in milliseconds
suppressrefresh: true // do not send updates if nothing changed
});
- var zwave = this.zwave;
console.log("new ZWaveController: %s", n);
var zwnodes = [];
- var nrNodeSubscriptions = {}; // 'event' => [node-red-node1_closure, ...]
+ var nrNodeSubscriptions = {}; // 'event' => [closure1, closure2...]
- // the fun part begins:
- function subscribe(nrNode, eventCallbacks) {
- Object.keys(eventCallbacks).forEach(function (evt) {
- var cb = eventCallbacks(evt);
- if (!(evt in nrNodeSubscriptions)) {
- nrNodeSubscriptions[evt] = [];
- };
- nrNodeSubscriptions[evt].push(cb);
- });
+ // subscribe a Node-Red node to ZWave events
+ this.subscribe = function(nrNode, event, callback) {
+ if (!(event in nrNodeSubscriptions))
+ nrNodeSubscriptions[event] = [];
+ nrNodeSubscriptions[event].push(callback);
}
-
- function zwcallback(evtName, args) {
- console.log("zwcallback(%s, %s)", evtName, args);
- if (evtName in nrNodeSubscriptions) {
- nrNodeSubscriptions[evt].forEach(
- function(callback) {
- console.log("calling %s", callback);
- callback.apply(this, args);
- });
+
+ // dispatch OpenZwave events onto all active Node-Red subscriptions
+ function zwcallback(event, arghash) {
+ console.log("zwcallback(event: %s, args: %j)", event, arghash);
+ if (event in nrNodeSubscriptions) {
+ nrNodeSubscriptions[event].forEach(function(callback) {
+ console.log("calling event callback %s with args %j", event, arghash);
+ callback.apply(this, arghash);
+ });
}
}
+ // see openzwave/cpp/src/Notification.cpp
+ function notificationText(a) {
+ switch(a){
+ case 0: return "message complete";
+ case 1: return "timeout";
+ case 2: return "nop";
+ case 3: return "node awake";
+ case 4: return "node asleep";
+ case 5: return "node dead";
+ case 6: return "node alive";
+ default: return "unknown OZW notification: "+a;
+ }
+ }
+
/* =============== Node-Red events ================== */
+ //
this.on("close", function() {
this.zwave.disconnect();
});
+
+
/* =============== OpenZWave events ================== */
- zwave.on('driver ready', function(homeid) {
- console.log('scanning homeid=0x%s...', homeid.toString(16));
- zwcallback('driver ready', homeid);
+ //
+ this.zwave.on('driver ready', function(homeid) {
+ this.homeid = homeid;
+ var homeHex = homeid.toString(16);
+ this.name='0x'+ homeHex;
+ console.log('scanning homeid=0x%s...', homeHex);
+ zwcallback('driver ready', {homeid: homeid});
});
+
//
- zwave.on('driver failed', function() {
+ this.zwave.on('driver failed', function() {
console.log('failed to start driver');
- zwcallback('driver failed');
+ zwcallback('driver failed', {});
zwave.disconnect();
// process.exit();
});
+
//
- zwave.on('node added', function(nodeid) {
+ this.zwave.on('node added', function(nodeid) {
zwnodes[nodeid] = {
manufacturer: '',
manufacturerid: '',
@@ -91,58 +114,70 @@ module.exports = function(RED) {
classes: {},
ready: false,
};
- zwcallback('node added', nodeid);
+ zwcallback('node added', {nodeid: nodeid});
});
- zwave.on('value added', function(nodeid, comclass, value) {
- // console.log('value added: %s', value)
+ //
+ this.zwave.on('value added', function(nodeid, comclass, valueId) {
if (!zwnodes[nodeid]['classes'][comclass])
zwnodes[nodeid]['classes'][comclass] = {};
- zwnodes[nodeid]['classes'][comclass][value.index] = value;
- //
- zwcallback('value added', comclass, value);
+ if (!zwnodes[nodeid]['classes'][comclass][valueId.instance])
+ zwnodes[nodeid]['classes'][comclass][valueId.instance] = {};
+ // add to cache
+ zwnodes[nodeid]['classes'][comclass][valueId.instance][valueId.index] = valueId;
+ // tell NR
+ zwcallback('value added', {
+ nodeid: nodeid, cmdclass: comclass, cmdinstance: valueId.instance, cmdidx: valueId.index,
+ currState: valueId['value'],
+ });
});
- zwave.on('value changed', function(nodeid, comclass, value) {
+ //
+ this.zwave.on('value changed', function(nodeid, comclass, valueId) {
+ // valueId: OpenZWave ValueID (struct) - not just a boolean
+ var oldst;
if (zwnodes[nodeid]['ready']) {
- var oldval = zwnodes[nodeid]['classes'][comclass][value.index]['value'];
- console.log('node%d: changed: %d:%s:%s->%s', nodeid, comclass,
- value['label'], oldval, value['value']); // new
+ oldst = zwnodes[nodeid]['classes'][comclass][valueId.instance][valueId.index]['value'];
+ console.log('node%d: changed: %d:%s:%s->%s', nodeid, comclass, valueId['label'], oldst, valueId['value']); // new
}
- //
- zwcallback('value changed', comclass, value);
+ // tell NR
+ zwcallback('value changed', {
+ nodeid: nodeid, cmdclass: comclass, cmdinstance: valueId.instance, cmdidx: valueId.index,
+ oldState: oldst, currState: valueId['value'],
+ });
// update cache
- zwnodes[nodeid]['classes'][comclass][value.index] = value;
+ zwnodes[nodeid]['classes'][comclass][valueId.instance][valueId.index] = valueId;
});
- zwave.on('value removed', function(nodeid, comclass, index) {
+ //
+ this.zwave.on('value removed', function(nodeid, comclass, index) {
if (zwnodes[nodeid] &&
zwnodes[nodeid]['classes'] &&
zwnodes[nodeid]['classes'][comclass] &&
zwnodes[nodeid]['classes'][comclass][index]) {
delete zwnodes[nodeid]['classes'][comclass][index];
- zwcallback('value deleted', nodeid, comclass, index);
+ zwcallback('value deleted', {
+ nodeid: nodeid, cmdclass: comclass, cmdinstance: valueId.instance, cmdidx: valueId.index,
+ oldState: oldval,
+ });
}
});
- zwave.on('node ready', function(nodeid, nodeinfo) {
- zwnodes[nodeid]['manufacturer'] = nodeinfo.manufacturer;
- zwnodes[nodeid]['manufacturerid'] = nodeinfo.manufacturerid;
- zwnodes[nodeid]['product'] = nodeinfo.product;
- zwnodes[nodeid]['producttype'] = nodeinfo.producttype;
- zwnodes[nodeid]['productid'] = nodeinfo.productid;
- zwnodes[nodeid]['type'] = nodeinfo.type;
- zwnodes[nodeid]['name'] = nodeinfo.name;
- zwnodes[nodeid]['loc'] = nodeinfo.loc;
+ //
+ this.zwave.on('node ready', function(nodeid, nodeinfo) {
+ zwnodes[nodeid]['manufacturer'] = nodeinfo.manufacturer;
+ zwnodes[nodeid]['manufacturerid'] = nodeinfo.manufacturerid;
+ zwnodes[nodeid]['product'] = nodeinfo.product;
+ zwnodes[nodeid]['producttype'] = nodeinfo.producttype;
+ zwnodes[nodeid]['productid'] = nodeinfo.productid;
+ zwnodes[nodeid]['type'] = nodeinfo.type;
+ zwnodes[nodeid]['name'] = nodeinfo.name;
+ zwnodes[nodeid]['loc'] = nodeinfo.loc;
zwnodes[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);
+ 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 zwnodes[nodeid]['classes']) {
switch (comclass) {
@@ -157,124 +192,40 @@ module.exports = function(RED) {
console.log('node%d: %s=%s', nodeid, values[idx]['label'], values[idx]['value']);
};
//
- zwcallback('node ready', nodeid, nodeinfo);
+ zwcallback('node ready', {nodeid: nodeid, nodeinfo: nodeinfo});
});
- 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;
- };
- zwcallback('notification', nodeid, notif);
+ //
+ this.zwave.on('notification', function(nodeid, notif) {
+ var s = notificationText(notif);
+ console.log('node%d: %s', nodeid, s);
+ zwcallback('notification', {nodeid: nodeid, notification: s});
});
- zwave.on('scan complete', function() {
+ //
+ this.zwave.on('scan complete', function() {
console.log('ZWave network scan complete.');
- zwcallback('scan complete');
+ zwcallback('scan complete', {});
});
- zwave.connect();
+ this.zwave.connect();
+
+ console.log('ZWave Driver Connected!');
}
//
RED.nodes.registerType("zwave-controller", ZWaveController);
//
-
- // =========================
- function ZWaveNode(config) {
- // =========================
- RED.nodes.createNode(this,config);
- this.name = config.name;
- var node = this;
- // set zwave node status initially as disconnected
- this.status({fill:"red",shape:"ring",text:"disconnected"});
- //
- var zwaveController = RED.nodes.getNode(config.controller);
- if (!zwaveController) {
- console.log('no ZWave controller class defined!');
- } else {
- /* =============== Node-Red events ================== */
- this.on("input", function(msg) {
- console.log("ZWaveNode=>input(%j)", msg);
- switch(true) {
- case /setLevel/.test(msg.topic):
- console.log('setting level %j', msg.payload);
- zwaveController.zwave.setLevel(
- msg.payload.nodeid,
- msg.payload.level
- );
- case /setValue/.test(msg.topic):
- console.log('setting value %j', msg.payload);
- zwaveController.zwave.setValue(
- msg.payload.nodeid,
- msg.payload.cmdclass,
- msg.payload.cmdidx,
- msg.payload.value
- );
- break;
- case /switchOn/.test(msg.topic):
- console.log('switching on %j', msg.payload);
- zwaveController.zwave.switchOn(msg.payload); break;
- case /switchOff/.test(msg.topic):
- console.log('switching off %j', msg.payload);
- zwaveController.zwave.switchOff(msg.payload); break;
- };
- });
- this.on("close", function() {
- console.log("ZWaveNode=>close()");
- this.zwave.disconnect();
- });
- this.on("error", function() {
- console.log("ZWaveNode=>error()");
- });
- /* =============== OpenZWave events ================== */
- zwaveController.subscribe(this, {
- 'node ready': function(nodeid, nodeinfo) {
- console.log('ZWaveNode ==> node %d ready, nodeinfo:%s', nodeid, nodeinfo);
- if (nodeid == this.nodeid) {
- this.status({fill:"green",shape:"dot",text:"connected"});
- }
- },
- 'value changed': function(val) {
- console.log('ZWaveNode ==> value changed');
- //
- }
- });
- }
- }
- RED.nodes.registerType("zwave-node", ZWaveNode);
-
+
// =========================
function ZWaveIn(config) {
// =========================
RED.nodes.createNode(this, config);
this.name = config.name;
- this.nodeid = config.nodeid;
- this.cmdclass = config.cmdclass;
- this.cmdidx = config.cmdidx;
//
var node = this;
- var ctrl = getZWaveController(config);
var zwaveController = RED.nodes.getNode(config.controller);
+
if (!zwaveController) {
node.err('no ZWave controller class defined!');
} else {
@@ -288,21 +239,24 @@ module.exports = function(RED) {
// what?
});
/* =============== OpenZWave events ================== */
- zwaveController.register(this, {
- 'driver ready': function(homeid) {
- node.homeid = homeid;
- this.status({fill:"green",shape:"dot",text: "0x"+homeid});
- },
- 'value changed': function(val) {
- console.log('node %d value changed');
- }
- });
+ zwaveController.subscribe(this, 'driver ready', function(homeid) {
+ node.homeid = homeid;
+ node.status({fill:"green",shape:"dot",text: "0x"+homeid});
+ });
+ zwaveController.subscribe(this, 'value changed', function(info) {
+ //console.log('zwave-in injecting: %s', info);
+ node.send(info);
+ });
+ zwaveController.subscribe(this, 'notification', function(notif) {
+ node.send(notif);
+ });
}
}
//
RED.nodes.registerType("zwave-in", ZWaveIn);
//
+
// =========================
function ZWaveOut(config) {
// =========================
@@ -313,7 +267,6 @@ module.exports = function(RED) {
this.cmdidx = config.cmdidx;
//
var node = this;
- var ctrl = getZWaveController(config);
var zwaveController = RED.nodes.getNode(config.controller);
if (!zwaveController) {
node.err('no ZWave controller class defined!');
@@ -321,51 +274,67 @@ module.exports = function(RED) {
// set zwave node status initially as disconnected
this.status({fill:"red",shape:"ring",text:"disconnected"});
/* =============== Node-Red events ================== */
+ //
this.on("input", function(msg) {
console.log("ZWaveOut#input: %j", msg);
+ var payload = JSON.parse(msg.payload);
switch(true) {
+ //
+ // switch On/Off: for basic single-instance switches and dimmers
+ //
+ case /switchOn/.test(msg.topic):
+ console.log('switching on %j', payload.nodeid);
+ zwaveController.zwave.switchOn(payload.nodeid); break;
+ case /switchOff/.test(msg.topic):
+ console.log('switching off %j', payload.nodeid);
+ zwaveController.zwave.switchOff(payload.nodeid); break;
+ //
+ // setLevel: for dimmers
+ //
case /setLevel/.test(msg.topic):
- console.log('setting level %j', msg.payload);
+ console.log('setting level %j', payload);
zwaveController.zwave.setLevel(
- this.nodeid,
- msg.payload
+ msg.payload.nodeid,
+ msg.payload.val
);
+ //
+ // setValue: for everything else
+ //
case /setValue/.test(msg.topic):
- console.log('setting value %j', msg.payload);
+ console.log('setting value %s', payload);
zwaveController.zwave.setValue(
- this.nodeid,
- this.cmdclass,
- this.cmdidx,
- msg.payload
+ payload.nodeid,
+ (payload.cmdclass || 37),// default cmdclass: on-off
+ (payload.cmdidx || 0), // default cmd index
+ (payload.cmdinstance|| 1), // default instance
+ (payload.val || 0) // default val
);
break;
- case /switchOn/.test(msg.topic):
- console.log('switching on %j', msg.payload);
- zwaveController.zwave.switchOn(this.nodeid); break;
- case /switchOff/.test(msg.topic):
- console.log('switching off %j', msg.payload);
- zwaveController.zwave.switchOff(this.nodeid); break;
};
});
+ //
this.on("close", function() {
zwaveController.zwave.disconnect();
});
+ //
this.on("error", function() {
});
+
/* =============== OpenZWave events ================== */
- zwaveController.register(this, {
- 'driver ready': function(homeid) {
- node.homeid = homeid;
- this.status({fill:"green",shape:"dot",text: "0x"+homeid});
- },
- 'value changed': function(val) {
- console.log('ZWaveOut node %d value changed');
- }
- });
+ //
+ zwaveController.subscribe(this, 'driver ready', function(homeid) {
+ node.homeid = homeid;
+ node.status({fill:"green",shape:"dot",text: "0x"+homeid});
+ });
+ //
+ zwaveController.subscribe(this, 'value changed', function(zwval) {
+ console.log('ZWaveOut value changed: %j', zwval);
+ });
}
}
//
RED.nodes.registerType("zwave-out", ZWaveOut);
//
+
}
diff --git a/README.md b/README.md
index a9e3ff2..ad423ea 100644
--- a/README.md
+++ b/README.md
@@ -12,9 +12,9 @@ OpenZWave nodes for node-red. Uses the OpenZWave binding for Node.JS (https://gi
-- {topic: 'switchOff', payload: 2} ==> switch off basic switch #2
- -- {topic: 'setLevel', payload: {nodeid: 5, level: 50}} ==> set level on dimmer #5 to 50%
+ -- {topic: 'setLevel', payload: {zwNode: 5, level: 50}} ==> set level on dimmer #5 to 50%
- -- {topic: 'setValue', payload: {nodeid: 8, cmdclass: 0x25, cmdidx:1, value: true}} ==> switch on the 2nd relay of multiswitch #8
+ -- {topic: 'setValue', payload: {zwNode: 8, cmdClass: 0x25, cmdIdx:1, value: true}} ==> switch on the 2nd relay of multiswitch #8
*'zwave-in' / 'zwave-out'*: use this to target a specific ZWave node's function ("ValueID" in OpenZWave terminology) that can be parameterised for individual ZWave device endpoints.
- 'Input nodes' can listen for value changes in the ZWave network so as to generate flow messages
diff --git a/zwave-example1.png b/zwave-example1.png
new file mode 100644
index 0000000..de307fe
Binary files /dev/null and b/zwave-example1.png differ