Controlling and monitoring your home with JavaScript.
Created by Kevin McDermott / @bigkevmcd
Uses the v8 JavaScript engine from Chrome.
Just like in the browser, fully asynchronous.
Used by tools like Grunt.js, Bower etc.
Receives and transmits in the 433.92Mhz range.
Translates the received transmissions into bytes and sends them down the USB wire.
Costs around £80 from specialist home-automation retailers
e.g. www.uk-automation.co.uk
or direct from www.rfxcom.com
Supported by a wide range of home-automation software - not all Open Source projects
Domoticz, DomotiGa, Domogik, Ed-win, EventGhost, FHEM, HomeAutom8, Homeseer HS2 and HS3, HomiDom, HouseAgent, Indigo, MeteoHub, OpenHAB, Open Source Automation, Beyond Measure, Digital Home Server, Heyu, Mydombox.com, VERA
Supports a huge range of "popular" hardware
var rfxcom = require('rfxcom');
var rfxtrx = new rfxcom.RfxCom('/dev/ttyUSB0'),
lightwaverf = new rfxcom.Lighting5(rfxtrx, rfxcom.lighting5.LIGHTWAVERF);
rfxtrx.on('security1', function (evt) {
if (evt.deviceStatus === rfxcom.security.MOTION) {
lightwaverf.switchOn('0xF09AC8/1');
} else if (evt.deviceStatus === rfxcom.security.NOMOTION) {
lightwaverf.switchOff('0xF09AC8/1');
}
});
rfxtrx.initialise(function () {
console.log('Device initialised');
});
Send a 14 byte Interface command – Reset packet (hex 0D 00 00 00 00 00 00 00 00 00 00 00 00 00) The RFXCOM will now stop the RF receive for 10 seconds. This period is terminated by sending a Status Request. Wait at least 50 milliseconds (max 9 seconds) then clear the COM port receive buffers. Send a 14 byte Interface command – Get Status packet (hex 0D 00 00 01 02 00 00 00 00 00 00 00 00 00) The RFXCOM will respond with the status and the 10 seconds reset timeout is terminated. If necessary send a select frequency selection command. The 433.92MHz transceiver does not have a frequency select and operates always on 433.92MHz. The RFXtrx is now ready to receive RF data and to receive commands from the application for the transmitter.
// This is a buffering parser which accumulates bytes until it receives the
// number of bytes specified in the first byte of the message.
// It relies on a flushed buffer, to ensure the first byte corresponds to the
// size of the first message.
// The 'data' message emitted has all the bytes from the message.
self.rfxtrxParser = function() {
var data = [],
requiredBytes = 0;
return function(emitter, buffer) {
// Collect data
data.push.apply(data, buffer);
if (requiredBytes === 0) {
requiredBytes = data[0] + 1;
}
if (data.length >= requiredBytes) {
emitter.emit("data", data.slice(0, requiredBytes));
data = data.slice(requiredBytes);
requiredBytes = 0;
}
};
};
RfxCom.prototype.security1Handler = function(data) {
var self = this,
subtype = data[0],
seqnbr = data[1],
id = "0x" + self.dumpHex(data.slice(2, 5), false).join(""),
deviceStatus = data[5] & ~0x80,
batterySignalLevel = data[6],
evt = {
subtype: subtype,
id: id,
deviceStatus: deviceStatus,
batteryLevel: batterySignalLevel & 0x0f,
rssi: batterySignalLevel >> 4,
tampered: data[5] & 0x80
};
self.emit("security1", evt);
};
rfxtrx.on('th3', function(evt){
// Oregon Scientific Temperature and Humidity
subtype: 0x03, // Different models indicated.
id: '0xFE01', // Unique per device
seqnbr: 0x09, // message id generated by device (or sent to device) mod 256
temperature: 0x14, // Degrees Celsius
humidity: 0x32, // Relative Humidity %
humidityStatus: 0x03, // Normal, Comfort, Dry, Wet
batteryLevel: 9, // 0-9 strength of battery
rssi: 4, // Signal strength
});
rfxtrx.on('elec2', function(evt){
// OWL Electricity Monitoring hardware
subtype: 0x01, // CM119/CM160 commonly available in the UK
id: '0xFE02', // Unique per device
seqnbr: 0x0A, // message id generated by device (or sent to device) mod 256
currentWatts: 370, // Currently detected energy consumption
totalWatts: 30225.82, // Total measured by device
batteryLevel: 9, // 0-9 strength of battery
rssi: 4, // Signal strength
});
rfxtrx.on('security1', function(evt){
// X10, KD101, Visonic, Meiantech, SA30 PIRs and Window Sensors
subtype: 0x01, // X10 Security hardware
id: '0xFE0204', // Unique per device
seqnbr: 0x0B, // message id generated by device (or sent to device) mod 256
deviceStatus: 0x04, // X10 security sensor motion detected
tampered: 0, // Tamper detection on devices
batteryLevel: 9, // 0-9 strength of battery
rssi: 4, // Signal strength
});
var rfxtrx = new rfxcom.RfxCom('/dev/ttyUSB0');
var lighting5 = new rfxcom.Lighting5(rfxtrx, rfxcom.lighting5.LIGHTWAVERF);
lighting5.switchOn('0xF09ACA/01');
rfxtrx.delay(3000); // Sleep for 3 seconds
lighting5.switchOff('0xF09ACA/01');
rfxtrx.delay(3000); // Sleep for 3 seconds
lighting5.switchOn('0xF09ACA/01', {level: 6});
var rfxtrx = new rfxcom.RfxCom('/dev/ttyUSB0');
var curtain1 = new rfxcom.Curtain1(rfxtrx);
curtain1.open('0x41/01');
rfxtrx.delay(3000); // Sleep for 3 seconds
curtain1.stop('0x41/01');
rfxtrx.delay(3000); // Sleep for 3 seconds
curtain1.close('0x41/01');
var rfxtrx = new rfxcom.RfxCom('/dev/ttyUSB0');
var lighting1 = new rfxcom.Lighting1(rfxtrx, rfxcom.lighting1.ARC);
lighting1.chime('C14');
var SunCalc = require('suncalc'), moment = require('moment');
var turnLightsFiftyPercent = function () {
var times = SunCalc.getTimes(moment(), 55.858, -4.259);
if (times.sunrise > new Date()) {
lightwave.switchOn('0xFAC271/1', {level: 0x10});
}
};
var job = new cronJob('00 50 06 * * 1-5', turnLightsFiftyPercent, null, true);
var job = new cronJob('00 55 06 * * 1-5', turnLightsFullOn, null, true);
rfxtrx.initialise(function (error) { if (error) { throw new Error('Unable to initialise the rfx device'); }; });
var turnLightsFullOn = function () {
var times = SunCalc.getTimes(moment(), 55.858, -4.259);
if (times.sunrise > moment().subtract('minutes', 5)) {
lightwave.switchOn('0xFAC271/1', {level: 0x1F});
}
};
var rfxcom = require('rfxcom'),
pg = require('pg').native,
conString = 'pg://user:password@localhost/user',
client = new pg.Client(conString);
var rfxtrx = new rfxcom.RfxCom('/dev/ttyUSB0');
rfxtrx.on('elec2', function (evt) {
// Requires a PostgreSQL table
// CREATE TABLE energy (recorded_time timestamp DEFAULT NOW(),
// device_id VARCHAR, current_watts FLOAT)
client.query('INSERT INTO energy(device_id, current_watts) values($1, $2)',
[evt.id, evt.currentWatts]);
});
rfxtrx.initialise(function () {
console.log('Device initialised');
});
server.get('/light/:device/:code/:option/', function (req, res, next) {
var deviceId = req.params.device + '/' + req.params.code,
statusCode = 200;
switch (req.params.option) {
case 'on':
lightwave.switchOn(deviceId);
break;
case 'off':
lightwave.switchOff(deviceId);
break;
default:
console.log('Unknown option', req.params.option);
statusCode = 400;
break;
}
res.send(statusCode);
return next();
});
describe('.switchOn', function(){
beforeEach(function (){
lighting5 = new rfxcom.Lighting5(device, rfxcom.lighting5.LIGHTWAVERF);
});
it('should send the correct bytes to the serialport', function(done){
var sentCommandId;
lighting5.switchOn('0xF09AC8/1', function(err, response, cmdId){
sentCommandId = cmdId;
done();
});
expect(fakeSerialPort).toHaveSent([0x0A, 0x14, 0x00, 0x00, 0xF0, 0x9A, 0xC8, 0x01, 0x01, 0x1F, 0x00]);
expect(sentCommandId).toEqual(0);
});
});
MQ Telemetry Transport (MQTT) - lightweight messaging protocol.
MQTT.js allows subscription to "topics" which can be used notify of events
e.g. subscribe /home/temperature/conservatory