Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ node_modules/
.settings/
*.swp
npm-debug.log
server.cert
server.key
10 changes: 10 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
language: node_js
sudo: required
node_js:
- "node"
env:
- CXX=g++-4.9
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.9
- gcc-4.9
script:
- npm run lint-js
- npm test
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
As of `v0.1.0`, this project adheres to [Semantic Versioning](http://semver.org/).

## Unreleased
* Adds support for gpio controlled devices (thanks @OvisMaximus)
* Improves log output on used configuration (thanks @OvisMaximus)

## [0.2.4] - 2016-01-13

Expand Down
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ You'll need to have [LIRC](http://lirc.org) installed and configured on your mac
npm install -g lirc_web
lirc_web

Note that you may need to run the `npm install` command with `sudo`.
Note that you probably have to run the `npm install -g` command with `sudo`.

### Viewing

Expand All @@ -36,11 +36,12 @@ You may place this configuration file in one of two locations and `lirc_web` wil
These are the available configuration options:

1. ``repeaters`` - buttons that repeatedly send their commands while pressed. A common example are the volume buttons on most remote controls. While you hold the volume buttons down, the remote will repeatedly send the volume command to your device.
2. ``macros`` - a collection of commands that should be executed one after another. This allows you to automate actions like "Play Xbox 360" or "Listen to music via AirPlay". Each step in a macro is described in the format ``[ "REMOTE", "COMMAND" ]``, where ``REMOTE`` and ``COMMAND`` are defined by what you have programmed into LIRC. You can add delays between steps of macros in the format of ``[ "delay", 500 ]``. Note that the delay is measured in milliseconds so 1000 milliseconds = 1 second.
2. ``macros`` - a collection of commands that should be executed one after another. This allows you to automate actions like "Play Xbox 360" or "Listen to music via AirPlay". Each step in a macro is described in the format ``[ "REMOTE", "COMMAND" ]``, where ``REMOTE`` and ``COMMAND`` are defined by what you have programmed into LIRC. You can add delays between steps of macros in the format of ``[ "delay", 500 ]``. Note that the delay is measured in milliseconds so 1000 milliseconds = 1 second. You can switch GPIO pins in the format of ``[ "gpio", "Receiver", 0 ] `` to set the gpio pin named "Receiver" to off.
3. ``commandLabels`` - a way to rename commands that LIRC understands (``KEY_POWER``, ``KEY_VOLUMEUP``) with labels that humans prefer (``Power``, ``Volume Up``).
4. ``remoteLabels`` - a way to rename the remotes that LIRC understands (``XBOX360``) with labels that humans prefer (``Xbox 360``).
5. ``blacklists`` - a way to hide unused commands from your remotes.
6. ``server`` - server configuration settings (ports, [SSL](http://serverfault.com/a/366374)).
7. ``gpios`` - configure gpio pins that drive some hardware like a relais switching the power supply of a device.


#### Example config.json:
Expand All @@ -62,6 +63,9 @@ These are the available configuration options:
},
"macros": {
"Play Xbox 360": [
[ "gpio", "TV", 1],
[ "gpio", "Receiver", 1],
[ "gpio", "Xbox", 1],
[ "SonyTV", "Power" ],
[ "delay", 500 ],
[ "SonyTV", "Xbox360" ],
Expand All @@ -71,10 +75,16 @@ These are the available configuration options:
[ "Xbox360", "Power" ]
],
"Listen to Music": [
[ "gpio", "Receiver", 1],
[ "Yamaha", "Power" ],
[ "delay", 500 ],
[ "Yamaha", "AirPlay" ]
]
],
"all off": [
[ "gpio", "TV", 0],
[ "gpio", "Receiver", 0],
[ "gpio", "Xbox", 0]
],
},
"commandLabels": {
"Yamaha": {
Expand All @@ -92,7 +102,15 @@ These are the available configuration options:
"AUX2",
"AUX3"
]
}
},
"gpios": [
{"name": "Receiver",
"pin": 19},
{"name": "TV",
"pin": 16},
{"name": "Xbox",
"pin": 13}
]
}

Please see the `example_configs/` directory.
Expand All @@ -107,10 +125,12 @@ API endpoints:
* ``GET`` ``/remotes.json`` - Returns all known remotes and commands
* ``GET`` ``/remotes/:remote.json`` - Returns all known commands for remote ``:remote``
* ``GET`` ``/macros.json`` - Returns all known macros
* ``GET`` ``/gpios.json`` - Returns all configured GPIO pins
* ``POST`` ``/remotes/:remote/:command`` - Send ``:command`` to ``:remote`` one time
* ``POST`` ``/remotes/:remote/:command/send_start`` - Begin sending ``:command``
* ``POST`` ``/remotes/:remote/:command/send_stop`` - Stop sending ``:command``
* ``POST`` ``/macros/:macro`` - Send all commands for ``:macro`` one time
* ``POST`` ``/gpios/:gpio-pin`` - Change the state of the pin


## Development
Expand All @@ -130,6 +150,7 @@ Install GruntJS (build environment):
npm install -g grunt-init
grunt server

Note that you probably have to run the `npm install -g` command with `sudo`.

**You may need to reload your shell before continuing so the Grunt binares are detected.**

Expand Down
63 changes: 54 additions & 9 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var swig = require('swig');
var labels = require('./lib/labels');
var https = require('https');
var fs = require('fs');
var gpio = require('./lib/gpio');
var macros = require('./lib/macros');

// Precompile templates
Expand All @@ -22,6 +23,8 @@ var app = module.exports = express();

// lirc_web configuration
var config = {};
var hasServerPortConfig = false;
var hasSSLConfig = false;

// Server & SSL options
var port = 3000;
Expand All @@ -41,21 +44,33 @@ app.use(compress());
app.use(express.static(__dirname + '/static'));

function _init() {
var home = process.env.HOME;
var searchPaths = [];

function configure(configFileName) {
searchPaths.push(configFileName);
config = require(configFileName);
console.log('Open Source Universal Remote is configured by ' + configFileName);
}

lircNode.init();

// Config file is optional
try {
try {
config = require(__dirname + '/config.json');
configure(__dirname + '/config.json');
} catch (e) {
config = require(home + '/.lirc_web_config.json');
configure(process.env.HOME + '/.lirc_web_config.json');
}
} catch (e) {
console.log('DEBUG:', e);
console.log('WARNING: Cannot find config.json!');
console.log('DEBUG: tried: ' + JSON.stringify(searchPaths));
}

hasServerPortConfig = config.server && config.server.port;
hasSSLConfig =
config.server && config.server.ssl && config.server.ssl_cert
&& config.server.ssl_key && config.server.ssl_port;
}

function refineRemotes(myRemotes) {
Expand Down Expand Up @@ -94,6 +109,7 @@ function refineRemotes(myRemotes) {
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development') {
lircNode.remotes = require(__dirname + '/test/fixtures/remotes.json');
config = require(__dirname + '/test/fixtures/config.json');
gpio.overrideWiringPi(require('./test/lib/wiring-pi-mock'));
} else {
_init();
}
Expand All @@ -110,6 +126,7 @@ app.get('/', function (req, res) {
remotes: refinedRemotes,
macros: config.macros,
repeaters: config.repeaters,
gpios: config.gpios,
labelForRemote: labelFor.remote,
labelForCommand: labelFor.command,
}));
Expand All @@ -135,6 +152,20 @@ app.get('/remotes/:remote.json', function (req, res) {
}
});

function respondWithGpioState(res) {
if (config.gpios) {
gpio.updatePinStates();
res.json(config.gpios);
} else {
res.send(404);
}
}

// List all gpio switches in JSON format
app.get('/gpios.json', function (req, res) {
respondWithGpioState(res);
});

// List all macros in JSON format
app.get('/macros.json', function (req, res) {
res.json(config.macros);
Expand All @@ -149,7 +180,6 @@ app.get('/macros/:macro.json', function (req, res) {
}
});


// Send :remote/:command one time
app.post('/remotes/:remote/:command', function (req, res) {
lircNode.irsend.send_once(req.params.remote, req.params.command, function () {});
Expand All @@ -171,21 +201,35 @@ app.post('/remotes/:remote/:command/send_stop', function (req, res) {
res.sendStatus(200);
});

// toggle /gpios/:gpio_pin
app.post('/gpios/:gpio_pin', function (req, res) {
var newValue = gpio.togglePin(req.params.gpio_pin);
res.setHeader('Cache-Control', 'no-cache');
res.json(newValue);
res.end();
});


// Execute a macro (a collection of commands to one or more remotes)
app.post('/macros/:macro', function (req, res) {
// If the macro exists, execute it
if (config.macros && config.macros[req.params.macro]) {
macros.exec(config.macros[req.params.macro], lircNode);
res.setHeader('Cache-Control', 'no-cache');
res.sendStatus(200);
if (config.gpios) {
respondWithGpioState(res);
} else {
res.sendStatus(200);
}
} else {
res.setHeader('Cache-Control', 'no-cache');
res.sendStatus(404);
}
});

gpio.init(config.gpios);

// Listen (http)
if (config.server && config.server.port) {
if (hasServerPortConfig) {
port = config.server.port;
}
// only start server, when called as application
Expand All @@ -195,13 +239,14 @@ if (!module.parent) {
}

// Listen (https)
if (config.server && config.server.ssl && config.server.ssl_cert && config.server.ssl_key && config.server.ssl_port) {
if (hasSSLConfig) {
sslOptions = {
key: fs.readFileSync(config.server.ssl_key),
cert: fs.readFileSync(config.server.ssl_cert),
};

https.createServer(sslOptions, app).listen(config.server.ssl_port);

console.log('Open Source Universal Remote UI + API has started on port ' + config.server.ssl_port + ' (https).');
console.log('Open Source Universal Remote UI + API has started on port '
+ config.server.ssl_port + ' (https).');
}
18 changes: 17 additions & 1 deletion example_configs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,19 @@
},
"macros": {
"Xbox360": [
[ "gpio", "TV", 1],
[ "gpio", "Receiver", 1],
[ "gpio", "Xbox", 1],
[ "SonyTV", "Power" ],
[ "SonyTV", "Xbox360" ],
[ "Yamaha", "Power" ],
[ "Yamaha", "Xbox360" ],
[ "Xbox360", "Power" ]
],
"all off": [
[ "gpio", "TV", 0],
[ "gpio", "Receiver", 0],
[ "gpio", "Xbox", 0]
]
},
"commandLabels": {
Expand All @@ -36,5 +44,13 @@
"AUX2",
"AUX3"
]
}
},
"gpios": [
{"name": "Receiver",
"pin": 19},
{"name": "TV",
"pin": 16},
{"name": "Xbox",
"pin": 13}
]
}
66 changes: 66 additions & 0 deletions lib/gpio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
var wpi = require('wiring-pi');
var gpios = null;

function updatePinStates() {
var i;
var gpio;
if (gpios) {
for (i = 0; i < gpios.length; i++) {
gpio = gpios[i];
gpio.state = wpi.digitalRead(gpio.pin);
}
}
}

function togglePin(pinId) {
var numericPinId = parseInt(pinId, 10);
var currentState = wpi.digitalRead(numericPinId);
var newState = currentState > 0 ? 0 : 1;
wpi.digitalWrite(numericPinId, newState);
return {
pin: numericPinId,
state: newState,
};
}

function findElement(array, propertyName, propertyValue) {
var i;
for (i = 0; i < array.length; i++) {
if (array[i][propertyName] === propertyValue) {
return array[i];
}
}
}

function getPinIdByName(pinName) {
var gpioPin = findElement(gpios, 'name', pinName);
return gpioPin.pin;
}

function setPin(pinName, newState) {
var numericPinId = getPinIdByName(pinName);
wpi.digitalWrite(numericPinId, newState);
}

function init(configuration) {
if (configuration) {
gpios = configuration;
wpi.setup('gpio');
updatePinStates();
}
}

function overrideWiringPi(wpiReplacement) {
var origWpi = wpi;
wpi = wpiReplacement;
return origWpi;
}

module.exports = {
init: init,
setPin: setPin,
overrideWiringPi: overrideWiringPi,
updatePinStates: updatePinStates,
togglePin: togglePin,
};
// vim: expandtab:tabstop=8:softtabstop=2:shiftwidth=2
4 changes: 3 additions & 1 deletion lib/labels.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module.exports = function Labels(remoteLabels, commandLabels) {
function getCommandLabel(remote, command) {
return commandLabels && commandLabels[remote] && commandLabels[remote][command] ? commandLabels[remote][command] : command;
return commandLabels && commandLabels[remote]
&& commandLabels[remote][command]
? commandLabels[remote][command] : command;
}

function getRemoteLabel(remote) {
Expand Down
Loading