rfxcom for Node.js – rfxtrx433 – USB radio transceiver



rfxcom for Node.js – rfxtrx433 – USB radio transceiver

1 0


domoticawithnode

Slides for my Home Automation with Node.js talk

On Github bigkevmcd / domoticawithnode

rfxcom for Node.js

Controlling and monitoring your home with JavaScript.

Created by Kevin McDermott / @bigkevmcd

Node.js

Uses the v8 JavaScript engine from Chrome.

Just like in the browser, fully asynchronous.

Used by tools like Grunt.js, Bower etc.

rfxtrx433

USB radio transceiver

Receives and transmits in the 433.92Mhz range.

Translates the received transmissions into bytes and sends them down the USB wire.

Availability

Costs around £80 from specialist home-automation retailers

e.g. www.uk-automation.co.uk

or direct from www.rfxcom.com

Software Support

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

Hardware Support

Supports a huge range of "popular" hardware

  • Power consumption - OWL devices
  • PIR Motion Detector - Marmitek MS90
  • Lighting control - LightwaveRF as seen in B&Q includes a switchable relay
  • Curtain control
  • Weather sensing - including Oregon Scientific
  • Power socket control - LightwaveRF and others

Devices

LightwaveRF lightswitch

LightwaveRF powerswitch

Power monitoring

node-rfxcom

Project

Let's see some JavaScript

                            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');
});
                        

Protocol

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);
};
                    

Emitted Events

Temperature

                        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
});
                    

Power

                      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
});
                    

Security

                      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
});
                    

All about Control

Lights PLEASE!

                      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});
                    

Curtain control

                      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');
                    

Doorbell

                      var rfxtrx = new rfxcom.RfxCom('/dev/ttyUSB0');
var lighting1 = new rfxcom.Lighting1(rfxtrx, rfxcom.lighting1.ARC);

lighting1.chime('C14');
                    

Integration with node-cron

                        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});
  }
};
                    

Power monitoring

                            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');
});
                        

REST API for lightswitch

                        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();
});

What's Supported?

Lighting

  • X10, ARC, ELRO, Waveman, EMW200, IMPULS, RisingSun, Philips (control coming soon)
  • AC, HomeEasy EU, ANSLUT
  • LightwaveRF, Siemens, EMW100, BBSB, MDREMOTE, RSL2, Livolo, RGB

Other sensors

  • OWL CM119/160/180 electricity monitors
  • X10, KD101, Visonic (433Mhz only), Meiantech, SA30 security sensors
  • Oregon Scientific temperature and humidity sensors
  • Oregon Scientific digital scales (BWR101/102 and GR102)

Testing with jasmine-node

  • 1096 lines in lib
  • 1249 lines in tests
                            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);
  });
});
                        

MQTT

MQ Telemetry Transport (MQTT) - lightweight messaging protocol.

https://github.com/adamvr/MQTT.js/

MQTT.js allows subscription to "topics" which can be used notify of events

e.g. subscribe /home/temperature/conservatory

Alternatives?

  • Jeenodes / Arduinos
  • ZWave
  • Insteon

More information

Why Node.js

THE END