ikettle-1.0-api
Version:
Node.js module to control iKettle 1.0, including discovery.
337 lines (301 loc) • 9.56 kB
JavaScript
var Promise = require('bluebird');
var net = require('net');
var _ = require('underscore');
var BitMask = require('bit-mask');
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var debug = require('debug')('ikettle::ikettle');
var iKettle = function(ipAddress, options) {
EventEmitter.call(this);
this.options = _.extendOwn({
port: 2000,
lineEnding: "\n" // apparently for newer 1.0 versions it uses /r/n
}, options);
this.ipAddress = ipAddress;
this.client = null;
this.isConnected = false;
this.status = {
temp: -1,
isWarming: false,
isOn: false,
hasProblem: false,
isRemoved: false
};
}
util.inherits(iKettle, EventEmitter);
iKettle.COMMAND = {
PING: "HELLOKETTLE",
GET_STATUS: "get sys status",
BOIL_100C: "set sys output 0x80",
BOIL_95C: "set sys output 0x2",
BOIL_80C: "set sys output 0x4000",
BOIL_65C: "set sys output 0x200",
WARM: "set sys output 0x8", // Same as pressing button on kettle
WARM_5_MINS: "set sys output 0x8005",
WARM_10_MINS: "set sys output 0x8010",
WARM_20_MINS: "set sys output 0x8020",
TURN_ON: "set sys output 0x4",
TURN_OFF: "set sys output 0x0",
};
iKettle.STATUS = {
PONG: 'HELLOAPP\r',
STATUS: 'sys status key=',
BOIL_100C: "sys status 0x100\r",
BOIL_95C: "sys status 0x95\r",
BOIL_80C: "sys status 0x80\r",
BOIL_65C: "sys status 0x65\r",
TEMP_REACHED: "sys status 0x3\r", // Reached Temperature
TURNED_ON: "sys status 0x5\r",
TURNED_OFF: "sys status 0x0\r",
WARM_5_MINS: "sys status 0x8005\r",
WARM_10_MINS: "sys status 0x8010\r",
WARM_20_MINS: "sys status 0x8020\r",
WARMING: "sys status 0x11\r",
WARMING_STOPPED: "sys status 0x10\r",
PROBLEM: "sys status 0x2\r", // Boil Dry?
REMOVED: "sys status 0x1\r", // Removed from base
};
iKettle.prototype._handleClose = function(context) {
var me = context;
debug('Connection to Kettle closed');
if (me.client) {
me.client.removeAllListeners();
}
me.client = null;
me.removeAllListeners();
me.isConnected = false;
};
iKettle.prototype._handleData = function(data, context) {
var me = context;
data = data.toString('ascii');
if (data.substr(0,iKettle.STATUS.STATUS.length) === iKettle.STATUS.STATUS) {
var mask = new BitMask(Buffer.from(data.slice(iKettle.STATUS.STATUS.length,data.length-1),'ascii').readUInt8());
// Bit6 Bit5 Bit4 Bit3 Bit2 Bit1
// 100C 95C 80C 65C Warm On
// 0x20 0x10 0x08 0x04 0x02 0x01
var isWarming = mask.getBit(2);
var isOn = mask.getBit(1);
var temp = -1;
debug({
6: mask.getBit(6),
5: mask.getBit(5),
4: mask.getBit(4),
3: mask.getBit(3),
2: mask.getBit(2),
1: mask.getBit(1)
});
if (mask.getBit(6))
temp = 100;
else if (mask.getBit(5))
temp = 95;
else if (mask.getBit(4))
temp = 80;
else if (mask.getBit(3))
temp = 65;
var status = {
temp: temp,
isWarming: isWarming,
isOn: isOn
};
debug('Got Status', status)
me.status = status;
me.emit('status',status);
} else if (data === iKettle.STATUS.BOIL_100C) {
me.status.temp = 100;
debug('Kettle is set to temperature 100c');
me.emit('boil 100c');
} else if (data === iKettle.STATUS.BOIL_95C) {
me.status.temp = 95;
debug('Kettle is set to temperature 95c');
me.emit('boil 95c');
} else if (data === iKettle.STATUS.BOIL_80C) {
me.status.temp = 80;
debug('Kettle is set to temperature 80c');
me.emit('boil 80c');
} else if (data === iKettle.STATUS.BOIL_65C) {
me.status.temp = 65;
debug('Kettle is set to temperature 65c');
me.emit('boil 65c');
} else if (data === iKettle.STATUS.TEMP_REACHED) {
debug('Kettle has reached desired temperature', me.status.temp);
me.emit('temp reached');
} else if (data === iKettle.STATUS.PROBLEM) {
me.status.hasProblem = true;
me.status.isOn = false;
debug('Kettle has a problem, maybe there is no water?');
me.emit('problem');
} else if (data === iKettle.STATUS.REMOVED) {
debug('Kettle has been removed from it\'s base');
me.status.isRemoved = true;
me.status.isOn = false;
me.emit('removed');
} else if (data === iKettle.STATUS.TURNED_OFF) {
debug('Kettle has turned off');
me.status.isOn = false;
me.emit('turned off');
} else if (data === iKettle.STATUS.TURNED_ON) {
debug('Kettle has turned on');
me.status.isOn = true;
me.emit('turned on');
} else if (data === iKettle.STATUS.WARMING) {
debug('Kettle keeping water warm');
me.emit('warming');
} else if (data === iKettle.STATUS.WARMING_STOPPED) {
debug('Kettle cancelled keeping water warm');
me.emit('warming stopped');
} else if (data === iKettle.STATUS.WARM_5_MINS) {
debug('Kettle is keeping water warm for 5 minutes');
me.emit('warm 5 mins');
} else if (data === iKettle.STATUS.WARM_10_MINS) {
debug('Kettle is keeping water warm for 10 minutes');
me.emit('warm 10 mins');
} else if (data === iKettle.STATUS.WARM_20_MINS) {
debug('Kettle is keeping water warm for 20 minutes');
me.emit('warm 20 mins');
} else if (data === iKettle.STATUS.PONG) {
debug('Kettle is alive');
me.emit('pong');
} else {
console.log('got data', data);
}
};
iKettle.prototype._sendCommand = function(command) {
if (!iKettle.COMMAND.hasOwnProperty(command))
throw new Error('Invalid Command: ' + command);
var me = this;
return new Promise(function(resolve, reject) {
debug('Sending Command', command, iKettle.COMMAND[command]);
me.client.write(iKettle.COMMAND[command] + me.options.lineEnding);
resolve();
});
}
iKettle.prototype._boil = function(temp, keepWarm) {
var me = this;
debug('Boiling for ' + temp + ' degrees');
var boilPromise = new Promise(function(resolve,reject) {
temp = parseInt(temp);
if (_.isNaN(temp)) {
return reject('Invalid Temperature Requested! Must be either 100,95,80 or 65');
}
switch (temp) {
case 100:
return me._sendCommand('BOIL_100C');
break;
case 95:
return me._sendCommand('BOIL_95C');
break;
case 80:
return me._sendCommand('BOIL_80C');
break;
case 65:
return me._sendCommand('BOIL_65C');
break;
default:
return reject('Invalid Temperature Requested! Must be either 100,95,80 or 65');
}
});
var keepWarmPromise = new Promise(function(resolve,reject) {
if (_.isNull(keepWarm) || _.isUndefined(keepWarm)) {
keepWarm = false;
resolve();
} else if (!_.isNaN(parseInt(keepWarm))) {
switch (parseInt(keepWarm)) {
case 5:
return me._sendCommand('WARM_5_MINS');
break;
case 10:
return me._sendCommand('WARM_10_MINS');
break;
case 20:
return me._sendCommand('WARM_20_MINS');
break;
default:
return reject('Must set timer to 5,10 or 20 minutes for keeping warm');
}
} else if (keepWarm) {
return me._sendCommand('WARM');
}
});
var turnOnPromise = new Promise(function(resolve,reject) {
me.turnOn.then(function() {
me.status.temp = temp;
resolve(temp);
});
});
return turnOnPromise.then(function() {
return boilPromise;
}).then(function() {
return keepWarmPromise;
});
}
iKettle.prototype.connect = function() {
var me = this;
return new Promise(function(resolve, reject) {
if (me.isConnected) {
debug('Already Connected to Kettle');
return resolve();
}
debug('Connecting to Kettle', me.ipAddress);
me.client = new net.Socket();
me.client.on('close', function() { me._handleClose(me) });
me.client.on('data', function(data) { me._handleData(data, me) });
me.client.connect(me.options.port, me.ipAddress, function() {
me.isConnected = true;
debug('Connected to Kettle', me.ipAddress);
return me.ping();
});
});
}
iKettle.prototype.disconnect = function() {
debug('Disconnect from Kettle', this.ipAddress);
this.client.destroy();
}
iKettle.prototype.boilWater = function(keepWarm) {
return this.boil100c(keepWarm);
}
iKettle.prototype.boilBlackTea = function(keepWarm) {
return this.boil95c(keepWarm);
}
iKettle.prototype.boilGreenTea = function(keepWarm) {
return this.boil80c(keepWarm);
}
iKettle.prototype.boilWhiteTea = function(keepWarm) {
return this.boil65c(keepWarm);
}
iKettle.prototype.boil100c = function(keepWarm) {
return this._boil(100, keepWarm);
}
iKettle.prototype.boil95c = function(keepWarm) {
return this._boil(95, keepWarm);
}
iKettle.prototype.boil80c = function(keepWarm) {
return this._boil(80, keepWarm);
}
iKettle.prototype.boil65c = function(keepWarm) {
return this._boil(65, keepWarm);
}
iKettle.prototype.turnOn = function() {
return this._command('TURN_ON').then(function() {
me.status.isOn = true;
});
}
iKettle.prototype.turnOff = function() {
return this._command('TURN_OFF').then(function() {
me.status.isOn = false;
});
}
iKettle.prototype.ping = function(context) {
context = context || this;
return context._sendCommand('PING', context);
}
iKettle.prototype.getStatus = function() {
var me = this;
return new Promise(function(resolve, reject) {
debug('Getting Status for Kettle', me.ipAddress);
me.once('status', function(status) {
resolve(status);
});
me._sendCommand('GET_STATUS');
});
}
module.exports = iKettle;