UNPKG

home-controller

Version:

A Node Home Automation Controller for Insteon Devices

484 lines (414 loc) 11.3 kB
var utils = require('./utils'); var Q = require('q'); var events = require('events'); var util = require('util'); var toByte = utils.toByte; var debug = require('debug')('home-controller:insteon:light'); function Light(id, insteon) { this.id = id; this.insteon = insteon; this.emitOnAck = true; } util.inherits(Light, events.EventEmitter); Light.prototype.turnOn = function (level, rate, next) { var id = this.id; if (typeof rate === 'function') { next = rate; rate = null; } else if (typeof level === 'function') { next = level; level = 100; rate = null; } else if (level === undefined){ level = 100; rate = null; } if (rate !== null && rate !== undefined) { switch(rate) { case 'slow': rate = 47000; break; case 'fast': rate = 100; break; } var rampAndLevel = utils.levelToHexHalfByte(level) + utils.rampRateToHexHalfByte(rate); return this.insteon.directCommand(id, '2E', rampAndLevel, next); } else { return this.insteon.directCommand(id, '11', utils.levelToHexByte(level), next); } }; Light.prototype.fan = function (speed, next) { var id = this.id; if (typeof speed === 'function') { next = speed; speed = null; } if(speed) { switch(speed) { case 'off': speed = 0x00; break; case 'low': speed = 0x3F; break; case 'high': speed = 0xFF; break; default: // med speed = 0xBF; } var cmd = { cmd1: '11', cmd2: toByte(speed), userData: ['02'], extended: true, isStandardResponse: true }; return this.insteon.directCommand(id, cmd, next); } else { var deferred = Q.defer(); deferred.resolve( this.insteon.directCommand(id, '19', '03') .then(function (status) { if(!status || !status.response || !status.response.standard) { debug('No response for fan request for device %s', id); return null; } var speed = parseInt(status.response.standard.command2, 16); if (speed === 0) { return 'off'; } else if (speed === 0xFF) { return 'high'; } else if (speed <= 0x7F) { return 'low'; } else { return 'medium'; } }) ); return deferred.promise.nodeify(next); } }; Light.prototype.fanOn = function (next) { return this.fan('medium', next); }; Light.prototype.fanOff = function (next) { return this.fan('off', next); }; Light.prototype.fanLow = function (next) { return this.fan('low', next); }; Light.prototype.fanMedium = function (next) { return this.fan('medium', next); }; Light.prototype.fanHigh = function (next) { return this.fan('high', next); }; /** * Turn Light On fast (no ramp) to 100% * * 12 -- ON FAST command * * @param {String} id * @param {Function} next */ Light.prototype.turnOnFast = function (next) { return this.insteon.directCommand(this.id, '12', next); }; /** * Turn Light Off * * 13 -- OFF command * * @param {String} id * @param {Function} next */ Light.prototype.turnOff = function (rate, next) { var id = this.id; if (typeof rate === 'function') { next = rate; rate = null; } if (rate !== null && rate !== undefined) { switch(rate) { case 'slow': rate = 47000; // 47 sec in msec break; case 'fast': rate = 100; // .1 sec in msec break; } var rampAndLevel = '0' + utils.rampRateToHexHalfByte(rate); return this.insteon.directCommand(id, '2F', rampAndLevel, next); } else { return this.insteon.directCommand(id, '13', next); } }; /** * Turn Light Off fast (no ramp) * * 13 -- OFF command * * @param {String} id * @param {Function} next */ Light.prototype.turnOffFast = function (next) { return this.insteon.directCommand(this.id, '14', next); }; /** * Brighten Light * * 15 -- Brighten command * * @param {String} id * @param {Function} next */ Light.prototype.brighten = function (next) { return this.insteon.directCommand(this.id, '15', next); }; /** * Dim Light * * 16 -- Dim command * * @param {String} id * @param {Function} next */ Light.prototype.dim = function (next) { return this.insteon.directCommand(this.id, '16', next); }; /** * Light Instant Change * Check for light level. * * 19 -- STATUS command * * 21 -- Instant Change command * * @param {String} id * @param {Number} level * @param {Function} next */ Light.prototype.level = function (level, next) { if (typeof level === 'function') { next = level; level = null; } var id = this.id; if (level !== null && level !== undefined){ return this.insteon.directCommand(id, '21', utils.levelToHexByte(level), next); } else { var deferred = Q.defer(); deferred.resolve( this.insteon.directCommand(id, '19') .then(function (status) { if(!status || !status.response || !status.response.standard) { debug('No response for level request for device %s', id); return null; } var level = Math.ceil(parseInt(status.response.standard.command2, 16) * 100 / 255); return level; }) ); return deferred.promise.nodeify(next); } }; Light.prototype.info = function (btn, next) { if (typeof btn === 'function') { next = btn; btn = 1; } if(btn === null || btn === undefined) { btn = 1; } var id = this.id; var cmd = {cmd1: '2E', cmd2: '00', extended: true}; btn = toByte(btn); cmd.userData = [btn]; var deferred = Q.defer(); deferred.resolve( this.insteon.directCommand(id, cmd) .then(function (status) { if(!status || !status.response || !status.response.extended) { debug('No response for info request for device %s', id); return null; } var rampRate = utils.byteToRampRate(status.response.extended.userData[6]); var onLevel = utils.byteToLevel(status.response.extended.userData[7]); var ledBrightness = parseInt(status.response.extended.userData[8], 16); return { rampRate: rampRate, onLevel: onLevel, ledBrightness: ledBrightness }; }) ); return deferred.promise.nodeify(next); }; Light.prototype.rampRate = function (btn, rate, next) { if (typeof btn === 'function') { next = btn; btn = 1; rate = null; } else if (typeof rate === 'function') { next = rate; rate = btn; btn = 1; } if(!btn) { btn = 1; } if (rate) { var id = this.id; var cmd = {cmd1: '2E', cmd2: '00', extended: true}; btn = toByte(btn); cmd.userData = [btn, '05', utils.rampRateToHexByte(rate)]; return this.insteon.directCommand(id, cmd).then(function(status) { if(!status || !status.response || !status.response.extended) { debug('No response after ramp rate set request for device %s', id); return null; } return utils.byteToRampRate(status.response.extended.userData[6]); }).nodeify(next); } var deferred = Q.defer(); deferred.resolve( this.info(btn) .then(function (info) { return (info || {}).rampRate; }) ); return deferred.promise.nodeify(next); }; Light.prototype.onLevel = function (btn, level, next) { if (typeof btn === 'function') { next = btn; btn = 1; level = null; } else if (typeof level === 'function') { next = level; level = btn; btn = 1; } if(!btn) { btn = 1; } if (level) { var id = this.id; var cmd = {cmd1: '2E', cmd2: '00', extended: true}; btn =toByte(btn); cmd.userData = [btn, '06', utils.levelToHexByte(level)]; return this.insteon.directCommand(id, cmd).then(function (status) { if (!status || !status.response || !status.response.extended) { debug('No response for onLevel request for device %s', id); return null; } return utils.byteToLevel(status.response.extended.userData[7]); }).nodeify(next); } var deferred = Q.defer(); deferred.resolve( this.info(btn) .then(function (info) { return (info || {}).onLevel; }) ); return deferred.promise.nodeify(next); }; Light.prototype.handleAllLinkBroadcast = function (group, cmd1, cmd2) { debug('Emitting BC command for device (%s) - group: %s, cmd1: %s, cmd2: %s', this.id, group, cmd1, cmd2); this.emit('command', group, cmd1, cmd2); switch (cmd1) { case '04': var level = Math.round((parseInt(cmd2, 16) / 0xff) * 100); this.emit('heartbeat', level); break; case '11': this.emit('turnOn', group); break; case '12': this.emit('turnOnFast', group); break; case '13': this.emit('turnOff', group); break; case '14': this.emit('turnOffFast', group); break; case '17': if(cmd2 === '00') { this.dimming = true; this.emit('dimming', group); } else { this.dimming = false; this.emit('brightening', group); } break; case '18': if(this.dimming) { this.emit('dimmed', group); } else { this.emit('brightened', group); } break; default: debug('No event for command - %s', cmd1); } }; Light.prototype.handleAck = function (cmd1, cmd2) { if(!!this.insteon && !!this.insteon.status && !!this.insteon.status.command) { var command = this.insteon.status.command; if (command.cmd1 === '19') { // ack for light status/level return; } if(command.extended && command.cmd1 === '2E' && command.id.toUpperCase() === this.id) { // ack for light.info return; } if(command.extended && command.cmd1 === '2F' && command.id.toUpperCase() === this.id) { // ack for insteon.linkAt return; } } if(!this.emitOnAck) { return; } debug('Emitting ACK command for device (%s) - cmd1: %s, cmd2: %s', this.id, cmd1, cmd2); var group = null; // act doesn't have group this.emit('command', group, cmd1, cmd2); switch (cmd1) { case '11': // turnOn case '21': // level this.emit('turnOn', group, Math.ceil(parseInt(cmd2, 16) * 100 / 255)); break; case '12': // turnOnFast this.emit('turnOnFast', group); break; case '2e': // turnOn at rampandlevel this.emit('turnOn', group, Math.ceil(parseInt(cmd2.substring(0,1), 16) * 100 / 15)); break; case '13': // turnOff case '14': // turnOffFast case '2f': // turnOff at ramp this.emit('turnOff', group); break; case '15': // brighten this.emit('brightened', group); break; case '16': // dim this.emit('dimmed', group); break; default: debug('No event for command - %s', cmd1); } }; Light.prototype.cancelPending = function() { this.insteon.cancelPending(this.id); }; module.exports = Light;