diff --git a/.gitignore b/.gitignore index 75ddea5..4e4c7c7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ sftp-config*.json d.ts +database diff --git a/README.md b/README.md index e04cd16..5770577 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ node-zwave Control your z-wave devices on node.js -THIS IS NOT READY! +THIS IS NOT READY! IT IS IN LARGE TORN APART, HARD CODED, AND NOT USEFUL FOR ANYTHING BUT MY STUFF. This code is essentially ported from a C library that does the same thing. The C lib was written by a genius and also a close friend whose blog resides here: http://www.squarepenguin.com/wordpress/ diff --git a/index.js b/index.js index a30de20..328452b 100644 --- a/index.js +++ b/index.js @@ -1,12 +1,39 @@ - var zwave = require('./src/node-zwave'); -var promise = zwave.connect(); +var promise = zwave.connect('/dev/ttyUSB0'); +//var promise = zwave.connect(); promise.then(function(connection) { console.log("I connected! Sweet!"); - zwave.getNodeAbilities(1); + //console.log(zwave); + + /* + var nodes = zwave.getNodes(); + nodes.then(function(data){ + console.log('woot! '+data); + }); + + + zwave.getNodes(function(data){ + //console.log('Woot!! '+data); + //console.log(data); + //console.log(data.length); + for(var i=0;i // 110 volts (6e) + // // Aftter setting to 120 + callback(data); + }); + return promise; // not sure if this promise works? +} + + +/******************************************************************************* + Setters +*******************************************************************************/ + +/** + * Set the voltage of the HEM + * @param {Number} nodeId The node ID of the HEM + * @param {Number} voltage The voltage to set it to, note, this is usually 120 for USA, or 240 outside the USA + * @param {Function} [callback] Callback is optional. A promise is returned + */ +energyMonitor.setVoltage = function(nodeId, voltage, callback) { + var paramId = 1; // voltage param + var paramLength = 2; + + var command = [ + COMMAND_CLASS_CONFIGURATION, + CONFIGURATION_SET, + paramId, + paramLength, + 0x00, // MSB + voltage // LSB + ]; + + command = prepData(nodeId,command,'config'); + + var promise = iface.sendMessage(command, 'request', listener); + promise.then(function(data){ + callback(data); + }); + return promise; // not sure if this promise works? +} + +/** + * Resets the configuration of the HEM + * @param {Number} nodeId The node ID of the HEM + * @param {Function} [callback] Callback is optional. A promise is returned + */ +energyMonitor.resetConfiguration = function(nodeId) { + var paramId = 255; // reset param + var paramLength = 1; + + var command = [ + COMMAND_CLASS_CONFIGURATION, + CONFIGURATION_SET + paramId, + paramLength, + 0x00 // ?? + ]; + + command = prepData(nodeId,command,'config'); + + var promise = iface.sendMessage(command, 'request', listener); + promise.then(function(data){ + callback(data); + }); + return promise; // not sure if this promise works? +} + +/** + * Resets the configuration of the HEM + * @param {Number} nodeId The node ID of the HEM + * @param {Number} group The group you would like to set the interval for (1,2, or 3) + * @param {Number} seconds The number of seconds between auto reports, default is 720, ma you can set till I fix this up is 255 + * @param {Function} [callback] Callback is optional. A promise is returned + */ +energyMonitor.setReportingInterval = function(nodeId, group, seconds) { + var paramId = INTERVAL_GROUPS[group]; + var paramLength = 4; + + var command = [ + COMMAND_CLASS_CONFIGURATION, + CONFIGURATION_SET + paramId, + paramLength, + 0x00, // MSB + 0x00, + 0x00, + seconds // LSB TODO: fix this so you can use the full 4 bytes ansd go above 255 seconds + ]; + + command = prepData(nodeId,command,'config'); + + var promise = iface.sendMessage(command, 'request', listener); + promise.then(function(data){ + callback(data); + }); + return promise; // not sure if this promise works? +} + +/** + * Resets the configuration of the HEM + * @param {Number} nodeId The node ID of the HEM + * @param {Number} group The group you would like to set the interval for (1,2, or 3) + * @param {Number} report Which report to send to that group (watt or kwh) + * @param {Function} [callback] Callback is optional. A promise is returned + */ +energyMonitor.addReportToGroup = function(nodeId, group, report) { + var paramId = REPORT_GROUPS[group]; + var paramLength = 4; + + var command = [ + COMMAND_CLASS_CONFIGURATION, + CONFIGURATION_SET + paramId, + paramLength, + 0x00, // MSB reserved + 0x00, // reserved + 0x00, // see page 6 of the engineering spec from aeon labs, this byte bit 0-5 allows for automatic individual clamp readings + REPORT_TYPES[report] // see page 6 of the engineering spec from aeon labs, this byte bit 0-3 allows for automatic overall readings + ]; + + command = prepData(nodeId,command,'config'); + + var promise = iface.sendMessage(command, 'request', listener); + promise.then(function(data){ + callback(data); + }); + return promise; // not sure if this promise works? +} + +energyMonitor.enableDeltaReporting = function(nodeId) { + // TODO: zwave.sendRequestData(4,[0x70,0x04,0x03,0x00,0x00],function(reply){console.log(reply);}); // Turn on Delta function of the whole HEM + +} + +// helper method to prepare the command packets to send +function prepData(nodeId,cmd,type) { + var mysteryVal = 0; + if(type=='config'){ + mysteryVal = 4+cmd[3]; + } + else if(type=='send'){ + mysteryVal = 2; + } + else if(type=request){ + mysteryVal = 3; + } + + var command = [ + 0x01, + 0x00, // length, calculated after we get a command + 0x00, // I believe this signifies it is a request? + defs.DATA, + nodeId, // nodeid + mysteryVal // This was required to get the HEM configuration working, probably breaks other stuff + ]; + + command = command.concat(cmd); + command.push(0x05); + command.push(0x03); + + command[1] = parseInt(command.length-1); + + return command; +} + +/******************************************************************************* + Listener - handles responses from zwave controller +*******************************************************************************/ + +function listener(data) { + console.log('Energy monitor response received...'); + console.log(data); +} + +module.exports = energyMonitor; + diff --git a/src/command_classes/index.js b/src/command_classes/index.js index e6a7f76..88594d1 100644 --- a/src/command_classes/index.js +++ b/src/command_classes/index.js @@ -3,5 +3,6 @@ var funcs = {}; funcs.thermostat = require('./thermostat'); funcs.binarySwitch = require('./binarySwitch'); +funcs.energyMonitor = require('./energyMonitor'); module.exports = funcs; \ No newline at end of file diff --git a/src/functions/definitions.js b/src/functions/definitions.js index 6d693ac..ef01ddc 100644 --- a/src/functions/definitions.js +++ b/src/functions/definitions.js @@ -1,14 +1,29 @@ - var d = {}; d.GET_NODES = 0x02; -d.APP_DATA = 0x04; +d.APP_DATA = 0x04; // this does not look to be correct? d.DATA = 0x13; -d.GET_NETWORK_ID = 0x20; +d.GET_NETWORK_ID = 0x20; // this does not look to be correct? d.GET_NODE_PROTOCOL = 0x41; -d.GET_NODE_ABILITIES = 0x49; -d.GET_NODE_SUPPORTED_CLASSES = 0x49; +d.GET_NODE_ABILITIES = 0x49; // this does not look to be correct? +d.GET_NODE_SUPPORTED_CLASSES = 0x49; // this does not look to be correct? d.GET_NODE_INFO = 0x60; +d.ADD_NODE_TO_NETWORK = 0x4a; +d.REMOVE_NODE_FROM_NETWORK = 0x4a; +d.DEVICE_TYPE = {0x01:{type:'basic controller'}, + 0x02:{type:'static controller'}, + 0x03:{type:'basic slave'}, + 0x04:{type:'routing slave'}, + 0x08:{type:'thermostat'}, + 0x10:{type:'switch'}, + 0x11:{type:'dimmer'}, + 0x12:{type:'basic switch remote'}, + 0x13:{type:'basic switch toggle'}, + 0x17:{type:'generic security panel'}, + 0x20:{type:'binary sensor'}, + 0x21:{type:'multilevel sensor'}, + 0x31:{type:'meter'} + }; module.exports = d; \ No newline at end of file diff --git a/src/functions/functions.js b/src/functions/functions.js index 885cb31..49cd9d6 100644 --- a/src/functions/functions.js +++ b/src/functions/functions.js @@ -1,13 +1,60 @@ var iface = require('../interface'); var defs = require('./definitions'); -function getNodes() { +function getNodes(cb) { + console.log('Getting list of nodes'); + + var command = [ + 0x01, + 0x04, // Length, including checksum which is added after + 0x00, + defs.GET_NODES, + 0xFE + ]; + + var promise = iface.sendMessage(command, 'request', listener); + promise.then(function(data){ + // Decode the nodes in the bitmask (byte 8 - 36) + var nodesFound = []; + if(data.length > 7){ + for(var i=7; i<36; i++){ + var base = ((i-7)*8)+1; + + for (var j = 0; j < 8; j++) { + if((data[i] >> j) & 1) { + nodesFound.push(base+j); + } + } + } + } + + cb(nodesFound); + }); } function getNodeAbilities(nodeId, cb) { console.log('Getting node abilities for node ' + nodeId); + var command = [ + 0x01, + 0x04, // Length, including checksum which is added after + 0x00, + defs.GET_NODE_ABILITIES, + nodeId + ]; + + var promise = iface.sendMessage(command, listener); + if(typeof callback === 'function') { + promise.then(callback); + } + return promise; + +} + +function getNodeInfo(nodeId, cb) { + console.log('Getting node info for node ' + nodeId); + var command = [ 0x01, 0x04, // Length, including checksum which is added after @@ -17,13 +64,91 @@ function getNodeAbilities(nodeId, cb) { ]; var promise = iface.sendMessage(command, listener); - if(typeof callback === 'Function') { + if(typeof callback === 'function') { promise.then(callback); } return promise; } +function getNodeProtocol(nodeId, cb) { + console.log('Getting node protocol for node ' + nodeId); + + var command = [ + 0x01, + 0x04, // Length, including checksum which is added after + 0x00, + defs.GET_NODE_PROTOCOL, + nodeId + ]; + + var promise = iface.sendMessage(command, 'request', listener); + promise.then(function(data){ + + var returnInfo = defs.DEVICE_TYPE[data[8]]; + returnInfo.nodeid = nodeId; + + console.log(returnInfo); + if(typeof cb === 'function') { + cb(returnInfo); + } + }).catch(function (error) { + console.log(error); + }); + +} + +function associateNode(nodeId, cb) { + console.log('Adding node to network ' + nodeId); + + var command = [ + 0x01, // SOC + 0x0B, // length (11), including the checksum which is added later + 0x00, // request + defs.DATA, // sending data + nodeId, // nodeid + 0x04, // ?? + 0x85, // COMMAND_CLASS_ASSOCIATION + 0x01, // ASSOCIATION_SET + 0x01, // _groupIdx + 0x01, // _targetNodeId + 0x05, // ?? + 0x03 // ?? + ]; + + var promise = iface.sendMessage(command, 'request', listener); + promise.then(function(data){ + + var returnInfo = data; + + console.log(returnInfo); + if(typeof cb === 'function') { + cb(returnInfo); + } + }).catch(function (error) { + console.log(error); + }); + +} + +function getNodeSupportedClasses(nodeId, cb) { + console.log('Getting node supported classes for node ' + nodeId); + + var command = [ + 0x01, + 0x04, // Length, including checksum which is added after + 0x00, + defs.GET_NODE_SUPPORTED_CLASSES, + nodeId + ]; + + var promise = iface.sendMessage(command, listener); + if(typeof callback === 'function') { + promise.then(callback); + } + return promise; + +} function listener(data) { console.log('Function response...'); @@ -33,5 +158,9 @@ function listener(data) { module.exports = { getNodes: getNodes, - getNodeAbilities: getNodeAbilities + getNodeAbilities: getNodeAbilities, + getNodeInfo: getNodeInfo, + getNodeProtocol: getNodeProtocol, + getNodeSupportedClasses: getNodeSupportedClasses, + associateNode: associateNode } \ No newline at end of file diff --git a/src/globals.js b/src/globals.js index 92537ea..520404e 100644 --- a/src/globals.js +++ b/src/globals.js @@ -2,6 +2,7 @@ var g = {}; g.ACK = 0x06; +g.NAK = 0x15; g.AUTO_ACK = 0x05; g.OPTION_ACK = 0x01; diff --git a/src/interface.js b/src/interface.js index e1389e4..5f39d9f 100644 --- a/src/interface.js +++ b/src/interface.js @@ -1,6 +1,7 @@ /** * Interface for communicating with zwave controller */ +var util = require('util'); var globals = require('./globals'); var Q = require('q'); var moment = require('moment'); @@ -12,7 +13,8 @@ var currentRequest = null; // The current request var serialPort = null; var messageHandler = {}; // Object to export -var SerialPort = require("serialport").SerialPort; +var serialport = require("serialport"); +var SerialPort = serialport.SerialPort; function createCallbackId() { currentCallbackId++; @@ -34,11 +36,11 @@ function generateChecksum(data) { } function runPendingRequest() { - console.log('Running pending request...'); - console.log(pendingRequests); + //console.log('Running pending request...'); + //console.log(pendingRequests); if(pendingRequests.length) { var request = pendingRequests.shift(); - messageHandler.sendMessage(request.message, request.responseType, request.listener); + messageHandler.sendMessage(request.message, request.responseType, request.listener, request.deferred); } } @@ -69,39 +71,49 @@ messageHandler.connect = function(serialPortAddress, callback) { return deferred.promise; } -messageHandler.sendMessage = function(messageArray, responseType, listener, useCallback) { +messageHandler.sendMessage = function(messageArray, responseType, listener, isPending, useCallback) { + var deferred = Q.defer(); + if(typeof responseType === 'function') { listener = responseType; responseType = 'response'; } if(currentState !== 'ready') { - console.log('Adding request to pending requests...'); + //console.log('Adding request to pending requests...'); pendingRequests.push({ message: messageArray, responseType: responseType, - listener: listener + listener: listener, + deferred: deferred // will need this when you call back later }); - return; + return deferred.promise; // have to return the promise so we know which request to call back } currentState = 'pendingAck'; - - var deferred = Q.defer(); if(useCallback) { messageArray.push(createCallbackId()); } messageArray.push(generateChecksum(messageArray)); messageArray[1] = messageArray.length - 2; - - console.log('Sending message to zwave controller'); - console.log(messageArray); - + + var logmsg = 'Sending message to zwave controller'; + + // pending requests have a different promise to worry about + if(isPending){ + deferred = isPending; + logmsg = 'Sending queued messages to zwave controller'; + } + currentRequest = { - responseType: responseType, - defer: deferred, - time: moment(), - listener: listener + responseType: responseType, + defer: deferred, + time: moment(), + listener: listener } + + console.log(logmsg); + console.log(messageArray); + var buffer = new Buffer(messageArray); serialPort.write(buffer); return deferred.promise; @@ -114,7 +126,15 @@ messageHandler.sendAck = function() { function listener(data) { console.log('Receiving data from zwave controller'); console.log(data); - if(data[0] == globals.ACK) { + + // TODO: sometimes the data will come bundled with a leading ack, if so we need to strip it + if(data[0] == globals.NAK) { + currentRequest.defer.resolve(false); + currentState = 'ready'; + currentRequest = null; + runPendingRequest() + } + else if(data[0] == globals.ACK) { console.log('Received ACK for request'); if(currentRequest.responseType == 'none') { currentRequest.defer.resolve(true); @@ -131,14 +151,21 @@ function listener(data) { console.log('Received response for request'); messageHandler.sendAck(); currentRequest.listener(data); + currentRequest.defer.resolve(data); currentState = 'ready'; currentRequest = null; runPendingRequest(); return; } else { + console.log('Catch broadcasted events here: '+moment().format('MMMM Do YYYY, h:mm:ss a')); messageHandler.sendAck(); - // Catch broadcasted events here... + + // Emit broadcasted events here... + EVENTS.emit('broadcast',data); + + runPendingRequest(); + } } diff --git a/src/node-zwave.js b/src/node-zwave.js index 287e5e9..cf9ebb9 100644 --- a/src/node-zwave.js +++ b/src/node-zwave.js @@ -1,16 +1,23 @@ - var cc = require('./command_classes/'); -// var funcs = require('./functions/'); var iface = require('./interface'); var functions = require('./functions/functions'); +var EventEmitter = require('events').EventEmitter; +EVENTS = new EventEmitter(); + var zwave = {}; // Basic functions +zwave.getNodes = functions.getNodes; zwave.getNodeAbilities = functions.getNodeAbilities; +zwave.getNodeInfo = functions.getNodeInfo; +zwave.getNodeProtocol = functions.getNodeProtocol; +zwave.getNodeSupportedClasses = functions.getNodeSupportedClasses; +zwave.associateNode = functions.associateNode; zwave.connect = iface.connect; zwave.thermostat = cc.thermostat; +zwave.energyMonitor = cc.energyMonitor; module.exports = zwave;