UNPKG

home-controller

Version:

A Node Home Automation Controller for Insteon Devices

555 lines (424 loc) 11.9 kB
var utils = require('./utils'); var toByte = utils.toByte; var convertTemp = utils.convertTemp; var util = require('util'); var Q = require('q'); var events = require('events'); var debug = require('debug')('home-controller:insteon:thermostat'); function Thermostat(id, insteon) { this.id = id; this.insteon = insteon; } util.inherits(Thermostat, events.EventEmitter); Thermostat.prototype.tempUp = function (change, next) { if(typeof change === 'function') { next = change; change = 1; } if(change === null || change === undefined) { change = 1; } change = toByte(change * 2); var cmd = { cmd1: '68', cmd2: change, extended: true, isStandardResponse: true }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.tempDown = function (change, next) { if(typeof change === 'function') { next = change; change = 1; } if(change === null || change === undefined) { change = 1; } change = toByte(change * 2); var cmd = { cmd1: '69', cmd2: change, extended: true, isStandardResponse: true }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.info = function (type, divisor, zone, next) { if(typeof zone === 'function') { next = zone; zone = 0; } zone = zone || 0; var cmd2 = toByte(type << 5 | zone); var deferred = Q.defer(); deferred.resolve( this.insteon.directCommand(this.id, '6A', cmd2) .then(function (status) { return parseInt(status.response.standard.command2, 16)/divisor; }) ); return deferred.promise.nodeify(next); }; Thermostat.prototype.temp = function (zone, next) { return this.info(0, 2, zone, next); }; Thermostat.prototype.setpoints = function (zone, next) { if(typeof zone === 'function') { next = zone; zone = 0; } if(!zone) { zone = 0; } var cmd2 = toByte(1 << 5 | zone); var deferred = Q.defer(); deferred.resolve( this.insteon.directCommand(this.id, {cmd1: '6A', cmd2: cmd2, responseCount: 2}) .then(function (status) { return status.response.standard.map(function (resp) { return parseInt(resp.command2, 16) / 2; }); }) ); return deferred.promise.nodeify(next); }; Thermostat.prototype.humidity = function (zone, next) { return this.info(3, 1, zone, next); }; var MODES = [ 'off', 'heat', 'cool', 'auto', 'fan', 'program', 'program heat', 'program cool', 'fan auto' ]; var MODE_CMDS = { off: '09', heat: '04', cool: '05', auto: '06', fan: '07', 'fan auto': '08', 'program heat': '0A', 'program cool': '0B', 'program auto': '0C' }; Thermostat.prototype.mode = function (mode, next) { if(typeof mode === 'function') { next = mode; mode = null; } if(mode) { var modeCmd = MODE_CMDS[mode.toLowerCase()]; if(!modeCmd) { return Q.fcall(function() { throw new Error('Invalid mode. Must be one of the following: ' + MODES.join(', ')); }).nodeify(next); } var cmd = { cmd1: '6B', cmd2: modeCmd, extended: true, exitOnAck: true }; return this.insteon.directCommand(this.id, cmd) .then(function(status) { if(!!status && status.success) { return mode; } else { return null; } }) .nodeify(next); } var deferred = Q.defer(); deferred.resolve( this.insteon.directCommand(this.id, '6B', '02') .then( function (status) { var mode = parseInt(status.response.standard.command2, 16); return MODES[mode]; }) ); return deferred.promise.nodeify(next); }; Thermostat.prototype.coolTemp = function (temp, next) { temp = toByte(temp * 2); var cmd = { cmd1: '6C', cmd2: temp, extended: true, isStandardResponse: true }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.heatTemp = function (temp, next) { temp = toByte(temp * 2); var cmd = { cmd1: '6D', cmd2: temp, extended: true, isStandardResponse: true }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.highHumidity = function (humidity, next) { var cmd = { cmd1: '2E', cmd2: '00', extended: true, exitOnAck: true, userData: ['01','0B', toByte(humidity)] }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.lowHumidity = function (humidity, next) { var cmd = { cmd1: '2E', cmd2: '00', extended: true, exitOnAck: true, userData: ['01','0C', toByte(humidity)] }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.backlight = function (delay, next) { var cmd = { cmd1: '2E', cmd2: '00', extended: true, exitOnAck: true, userData: ['01','05', toByte(delay)] }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.cycleDelay = function (delay, next) { var cmd = { cmd1: '2E', cmd2: '00', extended: true, exitOnAck: true, userData: ['01','06', toByte(delay)] }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.energyChange = function (change, next) { var cmd = { cmd1: '2E', cmd2: '00', extended: true, exitOnAck: true, userData: ['01','07', toByte(change)] }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.date = function (date, next) { if(typeof date === 'function') { next = date; date = new Date(); } if(!util.isDate(date)) { date = new Date(date); } var cmd = { cmd1: '2E', cmd2: '02', extended: true, exitOnAck: true, crc: true, userData: [ '02', toByte(date.getDay()), toByte(date.getHours()), toByte(date.getMinutes()), toByte(date.getSeconds()) ] }; return this.insteon.directCommand(this.id, cmd, next); }; Thermostat.prototype.details = function (next) { var cmdDataSet00 = { cmd1: '2E', cmd2: '00', extended: true, crc: true }; var cmdDataSet01 = { cmd1: '2E', cmd2: '00', extended: true, crc: true, userData: ['00', '00', '01'] }; var insteon = this.insteon; var id = this.id; var details = {}; var data; var deferred = Q.defer(); deferred.resolve( this.status() .then(function (status) { if(!!status) { details = status; } else { debug('No response for status command.'); return; } }). then(function() { return insteon.directCommand(id, cmdDataSet00); }) .then(function (status) { if(!status || !status.response || !status.response.extended) { debug('No response for status command set #1'); return; } data = status.response.extended.userData; details.backlight = parseInt(data[9], 16); details.delay = parseInt(data[10], 16); details.energyOffset = parseInt(data[11], 16); }) .then( function () { return insteon.directCommand(id, cmdDataSet01); }) .then(function (status) { if(!status || !status.response || !status.response.extended) { debug('No response for status command set #2'); return details; } data = status.response.extended.userData; details.setpoints = details.setpoints || {}; details.setpoints.highHumidity = parseInt(data[3], 16); details.setpoints.lowHumidity = parseInt(data[4], 16); return details; }) ); return deferred.promise.nodeify(next); }; Thermostat.prototype.status = function (next) { var cmd02 = { cmd1: '2E', cmd2: '02', extended: true, crc: true }; var insteon = this.insteon; var id = this.id; var details = {}; var data; var deferred = Q.defer(); deferred.resolve( insteon.directCommand(id, cmd02) .then(function (status) { if(!status || !status.response || !status.response.extended) { debug('No response for status command.'); return null; } if(!status.response.extended.userData || !status.response.extended.crc) { return null; } data = status.response.extended.userData; details.date = { day: parseInt(data[1], 16), hour: parseInt(data[2], 16), minute: parseInt(data[3], 16), seconds: parseInt(data[4], 16) }; var rawMode = parseInt(data[5], 16); var modes = ['off', 'auto', 'heat', 'cool', 'program']; details.mode = modes[rawMode >> 4]; details.fan = !!(rawMode & 1); details.setpoints = { cool: convertTemp('F', insteon.defaultTempUnits, parseInt(data[6], 16)), heat: convertTemp('F', insteon.defaultTempUnits, parseInt(data[11], 16)) }; details.humidity = parseInt(data[7], 16); var tempC = parseInt(data[8] + data[9], 16) / 10; details.temperature = convertTemp('C', insteon.defaultTempUnits, tempC); var rawState = parseInt(data[10], 16); details.cooling = !!(rawState & 1); details.heating = !!((rawState >> 1) & 1); details.energySaving = !!((rawState >> 2) & 1); details.unit = ((rawState >> 3) & 1) ? 'C' : 'F'; details.hold = !!((rawState >> 4) & 1); return details; }) ); return deferred.promise.nodeify(next); }; Thermostat.prototype.monitor = function (enable, next) { if(typeof enable === 'function') { next = enable; enable = true; } if(enable === null || enable === undefined) { enable = true; } var id = this.id; var insteon = this.insteon; if(enable) { return insteon.scene(id, 'gw', {group: 0xEF}) .then(function() { var cmdDataSet08 = { cmd1: '2E', cmd2: '00', extended: true, exitOnAck: true, userData: ['08'] }; return insteon.directCommand(id, cmdDataSet08, next); }); } return insteon.scene(id, null, {group: 0xEF, remove: true}, next); }; Thermostat.prototype.handleDirect = function (cmd1, cmd2) { this.emit('command', cmd1, cmd2); switch(cmd1) { case '6e': this.emit('status', { 'temperature': convertTemp('F', this.insteon.defaultTempUnits, 0.5 * cmd2) }); break; case '6f': this.emit('status', { 'humidity': cmd2 }); break; case '70': var fan = !!(cmd2 & 0x10); var modes = ['off', 'heat', 'cool', 'auto', 'program']; var mode = modes[cmd2 & 0x0F]; this.emit('status', { 'mode': mode, 'fan': fan }); break; case '71': this.emit('status', { 'coolSetpoint': cmd2 }); break; case '72': this.emit('status', { 'heatSetpoint': cmd2 }); break; default: debug('No event for command - %s', cmd1); } }; Thermostat.prototype.handleAllLinkBroadcast = function (group, cmd1) { this.emit('command', group, cmd1); switch(cmd1) { case '11': this.emit(['cooling', 'heating', 'highHumidity', 'lowHumidity'][group-1]); break; case '13': if(group === 3 || group === 4) { this.emit('normalHumidity'); } else { this.emit('off'); } break; default: debug('No event for command - %s', cmd1); } }; Thermostat.prototype.cancelPending = function() { this.insteon.cancelPending(this.id); }; module.exports = Thermostat;