Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
45a41f6
Separation of macro engine
OvisMaximus Jan 10, 2016
99df326
fake gpio to enable dev on mac and win
OvisMaximus Jan 30, 2016
483e8db
remove project name from dev environment config ignore
OvisMaximus Jan 30, 2016
6908d25
preparation to switch to file based gpio implementation
OvisMaximus Jan 31, 2016
e06f40d
refactoring: remove lint warning
OvisMaximus Jan 31, 2016
7a26ab9
initial implementation of 'gpio' lib abstraction
OvisMaximus Feb 1, 2016
2a48e3b
implementation of filebased gpio finished
OvisMaximus Feb 1, 2016
057b89b
cleanup: log output removed
OvisMaximus Feb 1, 2016
9d3668b
introduce example init.d/osur script
OvisMaximus Feb 2, 2016
e399ed4
refactoring: unique format
OvisMaximus Feb 2, 2016
a86b767
mention new features in example config, Changelog and Readme
OvisMaximus Feb 2, 2016
2f1d1c0
proceed with implementation of dependency resolving
OvisMaximus Feb 3, 2016
64381f8
refactoring: introduce state initialization to streamline tests
OvisMaximus Feb 4, 2016
58b8d6a
implemented recursive switching off depending on virtual states
OvisMaximus Feb 9, 2016
d3599be
change config name to osur
OvisMaximus Feb 22, 2016
9bc4aab
cleanup use of later defined function
OvisMaximus Feb 27, 2016
e5aa785
help user detect malicious configuration and fix spaces
OvisMaximus Feb 27, 2016
4501147
preserve state on reload
OvisMaximus Feb 27, 2016
0d740e0
don't add macros twice on reload
OvisMaximus Feb 27, 2016
8b07b12
don't call reset macros if not necessary
OvisMaximus Feb 27, 2016
99920b6
remove noise from test output
Jan 1, 2018
f2327e4
avoid bidirectional dependency
Jan 6, 2018
daa0244
remove gpio lib wiring pi
Jan 6, 2018
65fe787
fix missing library filebased gpio
Jan 6, 2018
6bd355b
adopt convention and make callback last arg
Jan 6, 2018
ed1f7d9
nail version of eslint due to upstream problems
Jan 6, 2018
29e5529
nail version of grunt due to upstream problems
Jan 6, 2018
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 @@ -7,3 +7,5 @@ node_modules/
npm-debug.log
server.cert
server.key
.idea
*.iml
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ 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)
* Allow macros call macros (thanks @OvixMaximus)
* Allow to define a default delay for a macro (thanks @OvisMaximus)

## [0.2.4] - 2016-01-13

Expand Down
58 changes: 32 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ These are the available configuration options:

#### Example config.json:


{
"server" : {
"port" : 3000,
Expand All @@ -61,31 +60,38 @@ These are the available configuration options:
"VolumeDown": true
}
},
"macros": {
"Play Xbox 360": [
[ "gpio", "TV", 1],
[ "gpio", "Receiver", 1],
[ "gpio", "Xbox", 1],
[ "SonyTV", "Power" ],
[ "delay", 500 ],
[ "SonyTV", "Xbox360" ],
[ "Yamaha", "Power" ],
[ "delay", 250 ],
[ "Yamaha", "Xbox360" ],
[ "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]
],
},
"macros": [
{
"name": "Xbox360",
"sequence": [
[ "gpio", "TV", 1 ],
[ "gpio", "Receiver", 1 ],
[ "gpio", "Xbox", 1 ],
[ "SonyTV", "Power" ],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it makes sense to add ir as the first parameter here to specify which communication protocol to use. This might make further enhancements (zwave, http) easier.

Copy link
Contributor Author

@OvisMaximus OvisMaximus Jan 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently lirc is a optional first argument to serve this purpose. Possible are any permutations of:

  • remove all code for the optional first argument (less code, more text in config files, less implicit config)
  • rename the optional argument to ir

I'd choose both, if you agree.

[ "SonyTV", "Xbox360" ],
[ "Yamaha", "Power" ],
[ "Yamaha", "Xbox360" ],
[ "Xbox360", "Power" ]
]},
{
"name": "lights off",
"defaultDelay": 40,
"sequence": [
[ "Lightcontrol", "C01off"],
[ "Lightcontrol", "C02off"],
[ "Lightcontrol", "C03off"]
]},
{
"name": "all off",
"defaultDelay": 20,
"sequence": [
[ "call", "lights off"],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that if this was called macro it would be more intuitive.

Copy link
Contributor Author

@OvisMaximus OvisMaximus Jan 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the sequence is just a property of the macro, but not the macro itself, it would lead to less intuitive expressions on the code side. Although I see your point, I'd suggest to keep it.

[ "gpio", "TV", 0 ],
[ "gpio", "Receiver", 0 ],
[ "gpio", "Xbox", 0 ]
]
}
],
"commandLabels": {
"Yamaha": {
"Power": "Power",
Expand Down
180 changes: 89 additions & 91 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
var express = require('express');
var logger = require('morgan');
var compress = require('compression');
var lircNode = require('lirc_node');
var lirc = require('./lib/lirc');
var consolidate = require('consolidate');
var swig = require('swig');
var labels = require('./lib/labels');
var https = require('https');
var fs = require('fs');
var macros = require('./lib/macro-manager.js');
var gpio = require('./lib/gpio');
var macros = require('./lib/macros');

// Precompile templates
var JST = {
Expand All @@ -21,7 +21,7 @@ var JST = {
// Create app
var app = module.exports = express();

// lirc_web configuration
// osur configuration
var config = {};
var hasServerPortConfig = false;
var hasSSLConfig = false;
Expand All @@ -43,7 +43,7 @@ app.set('view engine', 'jade');
app.use(compress());
app.use(express.static(__dirname + '/static'));

function _init() {
function readConfiguration() {
var searchPaths = [];

function configure(configFileName) {
Expand All @@ -52,14 +52,13 @@ function _init() {
console.log('Open Source Universal Remote is configured by ' + configFileName);
}

lircNode.init();

// Config file is optional
try {
try {
configure(__dirname + '/config.json');
} catch (e) {
configure(process.env.HOME + '/.lirc_web_config.json');
console.log('DEBUG:', e);
configure(process.env.HOME + '/.osur-config.json');
}
} catch (e) {
console.log('DEBUG:', e);
Expand All @@ -73,89 +72,95 @@ function _init() {
&& config.server.ssl_key && config.server.ssl_port;
}

function refineRemotes(myRemotes) {
var newRemotes = {};
var newRemoteCommands = null;
var remote = null;

function isBlacklistExisting(remoteName) {
return config.blacklists && config.blacklists[remoteName];
function overrideConfigurationForDebugOrDevelopment() {
var lircTest;
if (process.env.npm_package_config_test_env) {
lircTest = require('./test/lib/lirc');
lircTest.replaceLircByMock();
gpio.setGpioLibrary(require('./lib/gpio-la-mock'));
config = require('./test/fixtures/config.json');
hasServerPortConfig = false;
hasSSLConfig = false;
} else {
gpio.setGpioLibrary(require('./lib/gpio-la-gpio'));
}
}

function getCommandsForRemote(remoteName) {
var remoteCommands = myRemotes[remoteName];
var blacklist = null;

if (isBlacklistExisting(remoteName)) {
blacklist = config.blacklists[remoteName];
function initializeModules(done) {
function initializeMacros() {
var currentStates = macros.getCurrentStates();
macros.resetConfiguration();

remoteCommands = remoteCommands.filter(function (command) {
return blacklist.indexOf(command) < 0;
});
if (config.gpios) {
macros.registerDevice(gpio);
}

return remoteCommands;
macros.registerDevice(lirc);
macros.init(config.macros, currentStates);
}

for (remote in myRemotes) {
newRemoteCommands = getCommandsForRemote(remote);
newRemotes[remote] = newRemoteCommands;
}
lirc.init(config, function () {
if (config.gpios) {
gpio.init(config.gpios);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why gpio initialization is done within lirc.init callback?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Lirc consumes the whole configuration, including macros. This implies a lack of separation of concern, which leads to cross dependencies while initalizing both.

I'd prefer a initialization like iterate on configured modules, initialize them, initialize macros. This would imply a restructuring of the configuration.

Could we take it for now? I don't want to do two refactorings in one pull request. Or would you mind to suggest another aproach for perfoming initialisation?

}

return newRemotes;
}
if (config.macros) {
initializeMacros();
}

// Based on node environment, initialize connection to lircNode or use test data
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();
// initialize Labels for remotes / commands
labelFor = labels(config.remoteLabels, config.commandLabels);
done();
});
}

// initialize Labels for remotes / commands
labelFor = labels(config.remoteLabels, config.commandLabels);
function init(done) {
readConfiguration();
overrideConfigurationForDebugOrDevelopment();
initializeModules(done);
}

// Routes

// Index
app.get('/', function (req, res) {
var refinedRemotes = refineRemotes(lircNode.remotes);
res.send(JST.index({
remotes: refinedRemotes,
macros: config.macros,
var indexPage = JST.index({
remotes: lirc.getRemotes(),
macros: macros.getGuiMacroLabels(),
repeaters: config.repeaters,
gpios: config.gpios,
labelForRemote: labelFor.remote,
labelForCommand: labelFor.command,
}));
});
res.send(indexPage);
});

// Refresh
app.get('/refresh', function (req, res) {
_init();
res.redirect('/');
init(function () {
res.redirect('/');
});
});

// List all remotes in JSON format
app.get('/remotes.json', function (req, res) {
res.json(refineRemotes(lircNode.remotes));
res.json(lirc.getRemotes());
});

// List all commands for :remote in JSON format
app.get('/remotes/:remote.json', function (req, res) {
if (lircNode.remotes[req.params.remote]) {
res.json(refineRemotes(lircNode.remotes)[req.params.remote]);
var commands = lirc.getCommandsForRemote(req.params.remote);
if (commands) {
res.json(commands);
} else {
res.sendStatus(404);
}
});

function respondWithGpioState(res) {
if (config.gpios) {
gpio.updatePinStates();
res.json(config.gpios);
gpio.updatePinStates(config.gpios, function (result) {
res.json(result);
});
} else {
res.send(404);
}
Expand All @@ -171,49 +176,42 @@ app.get('/macros.json', function (req, res) {
res.json(config.macros);
});

// List all commands for :macro in JSON format
app.get('/macros/:macro.json', function (req, res) {
if (config.macros && config.macros[req.params.macro]) {
res.json(config.macros[req.params.macro]);
} else {
res.sendStatus(404);
}
});

// Send :remote/:command one time
app.post('/remotes/:remote/:command', function (req, res) {
lircNode.irsend.send_once(req.params.remote, req.params.command, function () {});
lirc.sendOnce(req.params.remote, req.params.command, function () {});
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a lot cleaner.

res.setHeader('Cache-Control', 'no-cache');
res.sendStatus(200);
});

// Start sending :remote/:command repeatedly
app.post('/remotes/:remote/:command/send_start', function (req, res) {
lircNode.irsend.send_start(req.params.remote, req.params.command, function () {});
lirc.sendStart(req.params.remote, req.params.command, function () {});
res.setHeader('Cache-Control', 'no-cache');
res.sendStatus(200);
});

// Stop sending :remote/:command repeatedly
app.post('/remotes/:remote/:command/send_stop', function (req, res) {
lircNode.irsend.send_stop(req.params.remote, req.params.command, function () {});
lirc.sendStop(req.params.remote, req.params.command, function () {});
res.setHeader('Cache-Control', 'no-cache');
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();
gpio.togglePin(req.params.gpio_pin, function (result) {
res.setHeader('Cache-Control', 'no-cache');
res.json(result);
res.end();
});
});


// Execute a macro (a collection of commands to one or more remotes)
app.post('/macros/:macro', function (req, res) {
if (config.macros && config.macros[req.params.macro]) {
macros.exec(config.macros[req.params.macro], lircNode);
var macroName = req.params.macro;
if (macros.isMacroDefined(macroName)) {
macros.execute(macroName);
res.setHeader('Cache-Control', 'no-cache');
if (config.gpios) {
respondWithGpioState(res);
Expand All @@ -226,27 +224,27 @@ app.post('/macros/:macro', function (req, res) {
}
});

gpio.init(config.gpios);

// Listen (http)
if (hasServerPortConfig) {
port = config.server.port;
}
// only start server, when called as application
if (!module.parent) {
app.listen(port);
console.log('Open Source Universal Remote UI + API has started on port ' + port + ' (http).');
}
init(function () {
// only start server, when called as application
if (!module.parent) {
// Listen (http)
if (hasServerPortConfig) {
port = config.server.port;
}

// Listen (https)
if (hasSSLConfig) {
sslOptions = {
key: fs.readFileSync(config.server.ssl_key),
cert: fs.readFileSync(config.server.ssl_cert),
};
app.listen(port);
console.log('Open Source Universal Remote UI + API has started on port ' + port + ' (http).');

https.createServer(sslOptions, app).listen(config.server.ssl_port);
// Listen (https)
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).');
}
}
});
Loading