diff --git a/.travis.yml b/.travis.yml index df63076..a159a61 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ language: node_js node_js: - - "0.10" - - "0.8" + - "5.5" + - "4.2" diff --git a/CHANGELOG.md b/CHANGELOG.md index bdd7e86..54a3d20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +## [1.0.0] - 2016-02-05 + +* Switching irsend from callbacks to promises. Breaking change. * Removing `Makefile` and just using `package.json` for running tests. ## [0.0.4] - 2015-12-30 diff --git a/README.md b/README.md index 5109dfc..136213c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ It is a part of the [Open Source Universal Remote](http://opensourceuniversalrem [![Build Status](https://travis-ci.org/alexbain/lirc_node.png)](https://travis-ci.org/alexbain/lirc_node) +Node.js >= v4.2 (LTS) required. + ## What is this? LIRC is a fantastic open source software package that allows you to send and @@ -62,13 +64,17 @@ answer any ambiguities. There are additional options that are not shown here. // Sending commands lirc_node = require('lirc_node'); - lirc_node.init(); - // To see all of the remotes and commands that LIRC knows about: - console.log(lirc_node.remotes); + // After lirc_node has initialized, see all of the remotes and commands that LIRC knows about: + lirc_node.init().then(function(remotes) { + console.log(remotes); + + // or + // console.log(lirc_node.remotes) + }) /* - Let's pretend that the output of lirc_node.remotes looks like this: + Output of lirc_node.remotes will look like this: { "tv": ["Power", "VolumeUp", "VolumeDown"], @@ -77,31 +83,37 @@ answer any ambiguities. There are additional options that are not shown here. */ // Tell the TV to turn on - lirc_node.irsend.send_once("tv", "power", function() { + lirc_node.irsend.send_once("tv", "Power").then(function() { console.log("Sent TV power command!"); }); // Tell the Xbox360 to turn on - lirc_node.irsend.send_once("xbox360", "power", function() { + lirc_node.irsend.send_once("xbox360", "Power").then(function() { console.log("Sent Xbox360 power command!"); }); + // Turn the volume up for 2 seconds + lirc_node.irsend.send_start("tv", "VolumeUp"); + setTimeout(function() { + lirc_node.irsend.send_stop("tv", "VolumeUp"); + }, 2000); - // Listening for commands + // Listen for all IR commands var listenerId = lirc_node.addListener(function(data) { console.log("Received IR keypress '" + data.key + "'' from remote '" + data.remote +"'"); }); + // Listen for a specific IR command lirc_node.addListener('KEY_UP', 'remote1', function(data) { + // `data` also has `code` and `repeat` properties from output of `irw` + console.log("Received IR keypress 'KEY_UP' from remote 'remote1'"); - // data also has `code` and `repeat` properties from the output of `irw` - // The final argument after this callback is a throttle allowing you to + + // The final argument after this callback is a throttle allowing you to // specify to only execute this callback once every x milliseconds. }, 400); - - ## Development Would you like to contribute to and improve this module? Fantastic. To contribute @@ -142,7 +154,7 @@ The exception to this would be refactoring existing code or changing documentati (The MIT License) -Copyright (c) 2013-2015 Alex Bain <alex@alexba.in> +Copyright (c) 2013-2016 Alex Bain <alex@alexba.in> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/lib/irsend.js b/lib/irsend.js index 16f964c..c3f6d37 100644 --- a/lib/irsend.js +++ b/lib/irsend.js @@ -1,9 +1,10 @@ -var exec = require('child_process').exec; +var Promise = require('bluebird'); +var exec = Promise.promisify(require('child_process').exec, { multiArgs: true }); -exports = module.exports = IRSend; - -function IRSend () { +// TODO: execOverride is used for testing. I don't like it. +function IRSend (execOverride) { this.command = 'irsend'; + this.exec = execOverride || exec; }; // In some cases the default lirc socket does not work @@ -16,34 +17,34 @@ IRSend.prototype.setSocket = function(socket) { } }; -IRSend.prototype.list = function(remote, code, callback) { +IRSend.prototype.list = function(remote, code) { var command = this._list(remote, code); - return exec(command, callback); + return this.exec(command); }; -IRSend.prototype.send_once = function(remote, code, callback) { +IRSend.prototype.send_once = function(remote, code) { var command = this._send_once(remote, code); - return exec(command, callback); + return this.exec(command); }; -IRSend.prototype.send_start = function(remote, code, callback) { +IRSend.prototype.send_start = function(remote, code) { var command = this._send_start(remote, code); - return exec(command, callback); + return this.exec(command); }; -IRSend.prototype.send_stop = function(remote, code, callback) { +IRSend.prototype.send_stop = function(remote, code) { var command = this._send_stop(remote, code); - return exec(command, callback); + return this.exec(command); }; -IRSend.prototype.set_transmitters = function(transmitters, callback) { +IRSend.prototype.set_transmitters = function(transmitters) { var command = this._set_transmitters(transmitters); - return exec(command, callback); + return this.exec(command); }; -IRSend.prototype.simulate = function(code, callback) { +IRSend.prototype.simulate = function(code) { var command = this._simulate(code); - return exec(command, callback); + return this.exec(command); }; // Internal methods @@ -103,3 +104,5 @@ IRSend.prototype._set_transmitters = function(transmitters) { IRSend.prototype._simulate = function(code) { return this.command + ' SIMULATE "' + code + '"'; }; + +exports = module.exports = IRSend; diff --git a/lib/lirc_node.js b/lib/lirc_node.js index edfd946..fca4543 100644 --- a/lib/lirc_node.js +++ b/lib/lirc_node.js @@ -1,10 +1,15 @@ -exports.version = '0.0.2'; +var Promise = require('bluebird'); + +// Setup irsend exports.IRSend = require('./irsend'); exports.irsend = new exports.IRSend(); exports.remotes = {}; +// Setup irreceive exports.IRReceive = require('./irreceive'); var irreceive = new exports.IRReceive(); + +// Setup event listeners for irreceive exports.addListener = irreceive.addListener.bind(irreceive); exports.on = exports.addListener; exports.removeListener = irreceive.removeListener.bind(irreceive); @@ -15,45 +20,64 @@ exports.setSocket = function(socket) { exports.irsend.setSocket(socket); } -exports.init = function(callback) { - exports.irsend.list('', '', irsendCallback); +exports.init = function() { + return exports.irsend.list('', '') + .then(exports._populateRemotes) + .then(exports._populateCommands) + .then(function(remotes) { + return exports.remotes = remotes; + }); +}; - function irsendCallback(error, stdout, stderr) { - exports._populateRemotes(error, stdout, stderr); - exports._populateCommands(); - if (callback) callback(); - } +// Parse the list of remotes that irsend knows about +exports._populateRemotes = function (irsendResult) { + var remotes = {}; + + irsendResult[1].split('\n').forEach(function (remote) { + var remoteName = remote.match(/\s(.*)$/); + if (remoteName) remotes[remoteName[1]] = []; + }); - return true; + return remotes; }; -// Private -exports._populateRemotes = function(error, stdout, stderr) { - var remotes = stderr.split('\n'); +// Given object whose keys represent remotes, get commands for each remote +// Returns promise that will resolve when all irsend invocations complete +exports._populateCommands = function (remotesObject) { + var commandPromises = []; + var remoteNames = Object.keys(remotesObject); - exports.remotes = {}; + remoteNames.forEach(function (remote) { + commandPromises.push(exports.irsend.list(remote, '') + .then(function (irsendResult) { + return { [remote]: exports._parseCommands(irsendResult) } + }) + ); + }) - remotes.forEach(function(element, index, array) { - var remoteName = element.match(/\s(.*)$/); - if (remoteName) exports.remotes[remoteName[1]] = []; - }); + return Promise.all(commandPromises) + .then(exports._joinRemoteCommands); }; -exports._populateCommands = function() { - for (var remote in exports.remotes) { - (function(remote) { - exports.irsend.list(remote, '', function(error, stdout, stderr) { - exports._populateRemoteCommands(remote, error, stdout, stderr); - }); - })(remote); - } +// Merges an array of remoteCommands into a single object +exports._joinRemoteCommands = function(remoteCommands) { + var remotes = {}; + + remoteCommands.forEach(function(remote) { + remotes = Object.assign(remotes, remote); + }); + + return remotes; }; -exports._populateRemoteCommands = function(remote, error, stdout, stderr) { - var commands = stderr.split('\n'); +// Parses the results of irsend for a specifif remote +exports._parseCommands = function (irsendResult) { + var commands = []; - commands.forEach(function(element, index, array) { - var commandName = element.match(/\s.*\s(.*)$/); - if (commandName && commandName[1]) exports.remotes[remote].push(commandName[1]); + irsendResult[1].split('\n').forEach(function (command) { + var commandName = command.match(/\s.*\s(.*)$/); + if (commandName && commandName[1]) commands.push(commandName[1]); }); + + return commands; }; diff --git a/package.json b/package.json index c4f4191..3a5e9c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lirc_node", - "version": "0.0.4", + "version": "1.0.0", "description": "Control LIRC from Node", "main": "lib/lirc_node.js", "scripts": { @@ -13,14 +13,20 @@ "keywords": [ "lirc" ], - "dependencies": {}, + "dependencies": { + "bluebird": "3.2.1" + }, "devDependencies": { "mocha": "2.3.3", "q": "1.4.1", "should": "7.1.0", - "sinon": "1.17.1" + "sinon": "1.17.1", + "sinon-as-promised": "4.0.0" }, "author": "Alex Bain ", + "engines" : { + "node" : ">=4.2" + }, "contributors": [ { "name": "Mark Stickley", diff --git a/test/irsend.js b/test/irsend.js index d06ad35..f919339 100644 --- a/test/irsend.js +++ b/test/irsend.js @@ -1,56 +1,97 @@ -var lirc_node = require('../'), +var IRSend = require('../lib/irsend'); assert = require('assert'), - sinon = require('sinon'); + sinon = require('sinon'), + child_process = require('child_process'); + +require('sinon-as-promised'); describe('IRSend', function() { - var irsend = new lirc_node.IRSend(); + var execStub = sinon.stub(child_process, 'exec').resolves(); + var irsend = new IRSend(execStub); + + beforeEach(function() { + }); + + afterEach(function() { + execStub.reset(); + }); describe('#list', function() { - it('should call the callback', function(done) { - irsend.list('yamaha', 'KEY_POWER', function() { + it('should call child_process.exec', function(done) { + irsend.list('yamaha', 'KEY_POWER').then(function() { + assert(execStub.calledOnce); done(); }); }); + + it('should return a promise', function(done) { + irsend.list('yamaha', 'KEY_POWER').then(function() { done(); }); + }); }); describe('#send_once', function() { - it('should call the callback', function(done) { - irsend.send_once('yamaha', 'KEY_POWER', function() { + it('should call child_process.exec', function(done) { + irsend.send_once('yamaha', 'KEY_POWER').then(function() { + assert(execStub.calledOnce); done(); }); }); + + it('should return a promise', function(done) { + irsend.send_once('yamaha', 'KEY_POWER').then(function() { done(); }); + }); }); describe('#send_start', function() { - it('should call the callback', function(done) { - irsend.send_start('yamaha', 'KEY_POWER', function() { + it('should call child_process.exec', function(done) { + irsend.send_start('yamaha', 'KEY_POWER').then(function() { + assert(execStub.calledOnce); done(); }); }); + + it('should return a promise', function(done) { + irsend.send_start('yamaha', 'KEY_POWER').then(function() { done(); }); + }); }); describe('#send_stop', function() { - it('should call the callback', function(done) { - irsend.send_stop('yamaha', 'KEY_POWER', function() { + it('should call child_process.exec', function(done) { + irsend.send_stop('yamaha', 'KEY_POWER').then(function() { + assert(execStub.calledOnce); done(); }); }); + + it('should return a promise', function(done) { + irsend.send_stop('yamaha', 'KEY_POWER').then(function() { done(); }); + }); }); describe('#simulate', function() { - it('should call the callback', function(done) { - irsend.simulate('00110101001 BEEP BEEP', function() { + it('should call child_process.exec', function(done) { + irsend.simulate('00110101001 BEEP BEEP').then(function() { + assert(execStub.calledOnce); done(); }); }); + + it('should return a promise', function(done) { + irsend.simulate('00110101001 BEEP BEEP').then(function() { done(); }); + }); }); describe('#set_transmitters', function() { - it('should call the callback', function(done) { - irsend.set_transmitters(1, function() { + it('should call child_process.exec', function(done) { + irsend.set_transmitters(1).then(function() { + assert(execStub.calledOnce); done(); }); }); + + it('should return a promise', function(done) { + irsend.set_transmitters(1).then(function() { done(); }); + }); }); // Internal methods diff --git a/test/lirc_node.js b/test/lirc_node.js index a6061fe..9191594 100644 --- a/test/lirc_node.js +++ b/test/lirc_node.js @@ -1,82 +1,72 @@ -var lirc_node = require('../'), +var lircNode = require('../'), assert = require('assert'), sinon = require('sinon'), q = require('q'); -describe('lirc_node', function() { +describe('lircNode', function() { describe('init', function() { it('should exist', function() { - assert(lirc_node.init instanceof Function); + assert(lircNode.init instanceof Function); }); }); describe("Setting an internal list of Remotes", function() { describe('#_populateRemotes', function() { beforeEach(function() { - lirc_node.remotes = {}; + lircNode.remotes = {}; }); it('should exist', function() { - assert(lirc_node._populateRemotes instanceof Function); + assert(lircNode._populateRemotes instanceof Function); }); - it('should set exports.remotes as an object', function() { - lirc_node._populateRemotes('', '', ''); - assert(lirc_node.remotes instanceof Object); + it('should return an object of remote names', function () { + var remotes = lircNode._populateRemotes(['', '']); + assert(remotes instanceof Object); }); it('should set exports.remotes to have keys for a single remote name', function() { - var stderr = "irsend: SonyTV"; + var result = ['', 'irsend: SonyTV']; + var remotes = lircNode._populateRemotes(result); - lirc_node._populateRemotes('', '', stderr); - - assert(Object.keys(lirc_node.remotes).length === 1); - assert(lirc_node.remotes["SonyTV"] instanceof Array); + assert(Object.keys(remotes).length === 1); + assert(remotes.SonyTV instanceof Array); }); - it('should set exports.remotes to have keys for all remote names', function() { - var stderr = "irsend: Yamaha\nirsend: Arizer\nirsend: Microsoft_Xbox360"; - - lirc_node._populateRemotes('', '', stderr); + it('should set exports.remotes to have keys for all remote names', function () { + var result = ['', 'irsend: Yamaha\nirsend: Arizer\nirsend: Microsoft_Xbox360']; + var remotes = lircNode._populateRemotes(result); - assert(Object.keys(lirc_node.remotes).length === 3); - assert(lirc_node.remotes["Yamaha"] instanceof Array); - assert(lirc_node.remotes["Arizer"] instanceof Array); - assert(lirc_node.remotes["Microsoft_Xbox360"] instanceof Array); + assert(Object.keys(remotes).length === 3); + assert(remotes.Yamaha instanceof Array); + assert(remotes.Arizer instanceof Array); + assert(remotes.Microsoft_Xbox360 instanceof Array); }); }); describe('#_populateCommands', function() { beforeEach(function() { - sinon.stub(lirc_node.irsend, 'list', function() {}); + sinon.stub(lircNode.irsend, 'list', function() {}); }); afterEach(function() { - lirc_node.remotes = {}; - lirc_node.irsend.list.restore(); + lircNode.remotes = {}; + lircNode.irsend.list.restore(); }); it('should exist', function() { - assert(lirc_node._populateCommands instanceof Function); + assert(lircNode._populateCommands instanceof Function); }); }); - describe('#_populateRemoteCommands', function() { - beforeEach(function() { - lirc_node.remotes = {}; - }); - - it('should exist', function() { - assert(lirc_node._populateRemoteCommands instanceof Function); - }); - - it('should push commands onto the remote\'s array', function() { + describe('#_parseCommands', function () { + it('should return an array with the remote\'s commands', function () { var remote = 'Microsoft_Xbox360'; - var stderr = 'irsend: 0000000000000bd7 OpenClose\nirsend: 0000000000000b9b XboxFancyButton'; - lirc_node.remotes[remote] = []; - lirc_node._populateRemoteCommands(remote, '', '', stderr); - assert(lirc_node.remotes[remote][0] == 'OpenClose'); - assert(lirc_node.remotes[remote][1] == 'XboxFancyButton'); + var result = ['', 'irsend: 0000000000000bd7 OpenClose\nirsend: 0000000000000b9b XboxFancyButton']; + lircNode.remotes[remote] = []; + var commands = lircNode._parseCommands(result); + assert(commands[0] === 'OpenClose'); + assert(commands[1] === 'XboxFancyButton'); }); }); });