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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ node_modules/
.settings/
*.swp
npm-debug.log
zwscene.xml
zwcfg*
OZW_Log.txt
124 changes: 113 additions & 11 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ var consolidate = require('consolidate');
var swig = require('swig');
var labels = require('./lib/labels');
var https = require('https');
var request = require('request');
var fs = require('fs');
var macros = require('./lib/macros');
var request = require('request');
var OpenZWave = require('openzwave-shared');

// Precompile templates
var JST = {
Expand Down Expand Up @@ -47,7 +50,10 @@ app.use(express.static(__dirname + '/static'));
function _init() {
var home = process.env.HOME;

lircNode.init();
lircNode.init().then(function() {
console.log("LIRC connection initialized");
console.log(lircNode.remotes);
});

// Config file is optional
try {
Expand Down Expand Up @@ -117,8 +123,10 @@ labelFor = labels(config.remoteLabels, config.commandLabels);
// Index
app.get('/', function (req, res) {
var refinedRemotes = refineRemotes(lircNode.remotes);

res.send(JST.index({
remotes: refinedRemotes,
devices: config.devices,
macros: config.macros,
repeaters: config.repeaters,
labelForRemote: labelFor.remote,
Expand All @@ -139,6 +147,14 @@ app.get('/refresh', function (req, res) {
res.redirect('/');
});

// Get all capabilities of remote
Copy link
Author

Choose a reason for hiding this comment

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

Greate idea

app.get('/capabilities.json', function(req, res) {
res.json({
"devices": config.devices,
"remotes": lirc_node.remotes
});
});

// List all remotes in JSON format
app.get('/remotes.json', function (req, res) {
res.json(refineRemotes(lircNode.remotes));
Expand Down Expand Up @@ -167,6 +183,66 @@ app.get('/macros/:macro.json', function (req, res) {
}
});

// List all devices in JSON format
app.get('/devices.json', function(req, res) {
// TODO: Should return a nicely formatted response, not just the config object
res.json(config.devices);
});

// List all commands for :device in JSON format
app.get('/devices/:device.json', function(req, res) {
if (config.devices && config.devices[req.params.device]) {
// TODO: Should return a nicely formatted response, not just the config object
res.json(config.devices[req.params.device]);
} else {
res.send(404);
}
});

// Get device state
app.get('/devices/:device', function(req, res) {
if (config.devices &&
config.devices[req.params.device] &&
config.devices[req.params.device]['state']) {

var state = config.devices[req.params.device]['state'];

// Build request to make to get device state
var stateReq = {
method: state.method || "GET",
url: state.url,
form: state.body
};

// Make request for state, include body of response in response
request(stateReq, function (error, response, body) {
// TODO: How to parse the response and only return relevant portion?
var bodyJson = JSON.parse(body);
res.json(bodyJson);
});

} else {
// Device doesn't have a state, return 404
res.send(404);
}
});

// Execute device action
app.post('/devices/:device/:command', function(req, res) {
Copy link
Author

Choose a reason for hiding this comment

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

How is this related to the direct zwave endpoint? this is more flexible and follows the remote endpoint. Also maybe use a get to get the current status? Most of these devices have two-way communication and the status can be queried.

Copy link
Owner

Choose a reason for hiding this comment

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

I was trying to define what an abstract device would need and settled on two types of functionality:

  • Getting state
  • Triggering commands

So you could "GET" the device to trigger requesting the current state, or you could POST to an endpoint to trigger a command.

IR devices wouldn't have state, though.

Copy link
Contributor

Choose a reason for hiding this comment

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

I need some clarity when talking about device - in the past hours I used
the Term "device" for 'lirc', 'gpio' or 'zigbee' endpoints from the macro
engine perspective.

If I got it right, you are using 'device' for a single device like the
receiver. In case of the Denon Receiver I own, I really want to use the IR
to change its settings, but i can fetch its state via web interface. The
interface is very slow when changing its state.

Bottomline: I think it is possible if not probable that we'll see IR
devices with physical state...

On Sun, Jan 17, 2016 at 11:20 PM, Alex Bain notifications@github.com
wrote:

In app.js
#36 (comment):

  •    // Make request for state, include body of response in response
    
  •    request(stateReq, function (error, response, body) {
    
  •      // TODO: How to parse the response and only return relevant portion?
    
  •      var bodyJson = JSON.parse(body);
    
  •      res.json(bodyJson);
    
  •    });
    
  • } else {
  •    // Device doesn't have a state, return 404
    
  •    res.send(404);
    
  • }
    +});

+// Execute device action
+app.post('/devices/:device/:command', function(req, res) {

I was trying to define what an abstract device would need and settled on
two types of functionality:

  • Getting state
  • Triggering commands

So you could "GET" the device to trigger requesting the current state, or
you could POST to an endpoint to trigger a command.

IR devices wouldn't have state, though.


Reply to this email directly or view it on GitHub
https://github.com/alexbain/lirc_web/pull/36/files#r49953537.

Copy link
Owner

Choose a reason for hiding this comment

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

Yeah, I think that a 'device' should represent a physical artifact within the region the universal remote is responsible for controlling. A device could use any number of protocols but would be presented in a 'unified' way for the UI / API to interact with.

I hadn't considered a device that uses different protocols to get state vs send commands from, but it seems like something that could be supported.

var device = config.devices[req.params.device];
var command = device.commands[req.params.command];

var commandReq = {
method: command.method,
url: command.url,
form: command.body
};

request(commandReq);

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

// Send :remote/:command one time
app.post('/remotes/:remote/:command', function (req, res) {
Expand Down Expand Up @@ -202,24 +278,50 @@ app.post('/macros/:macro', function (req, res) {
}
});

// ZWAVE
var zwaveNodes = [];
var zwave = new OpenZWave({ saveconfig: true });
zwave.connect('/dev/ttyACM0');

app.post('/zwave/:id/on', function(req, res) {
var id = req.params.id;
zwave.setValue(id, 37, 1, 0, true);
res.json({ "success": true });
});

app.post('/zwave/:id/off', function(req, res) {
var id = req.params.id;
zwave.setValue(id, 37, 1, 0, false);
res.json({ "success": true });
});

zwave.on('scan complete', function() {
console.log("Zwave scan complete...");
});

// Listen (http)
if (config.server && config.server.port) {
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).');
}

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

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

process.on('SIGINT', function() {
zwave.disconnect('/dev/ttyACM0');
process.exit();
});
2 changes: 1 addition & 1 deletion lib/macros.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function exec(macro, lircNode, iter) {
}, command[1]);
} else {
// By default, wait 100msec before calling next command
lircNode.irsend.send_once(command[0], command[1], function () {
lircNode.irsend.send_once(command[0], command[1]).then(function() {
setTimeout(function () {
exec(macro, lircNode, i);
}, 100);
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"lirc_node": "0.0.4",
"lodash": "^3.10.1",
"morgan": "^1.6.1",
"openzwave-shared": "^1.1.7",
"swig": "^1.4.2"
},
"devDependencies": {
Expand All @@ -30,6 +31,7 @@
"mocha": "2.3.4",
"nodemon": "1.8.1",
"should": "^8.1.1",
"request": "~2.53.0",
"sinon": "1.17.2",
"supertest": "1.1.0"
},
Expand Down
15 changes: 12 additions & 3 deletions static/css/app.less
Original file line number Diff line number Diff line change
Expand Up @@ -108,23 +108,30 @@ h1 {
background: #5dade2 !important;
}

.device-link.active {
background: #5dade2 !important;
}

.btn-wide {
padding-left: 0;
padding-right: 0;
width: 100%;
}

.remote {
.remote,
.device {
margin: 0;
padding: 0;
position: relative;
}

.remotes-nav,
.devices-nav,
.macros-nav {
}

.remotes-nav li,
.devices-nav li,
.macros-nav li {
margin: 20px 0;
}
Expand Down Expand Up @@ -153,11 +160,13 @@ a:active,
margin: 0 40px;
}

.remote {
.remote,
.device {
display: none;
}

.remote.active {
.remote.active,
.device.active {
display: block;
}

Expand Down
13 changes: 10 additions & 3 deletions static/css/compiled/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,22 @@ h1.is-remote {
.remote-link.active {
background: #5dade2 !important;
}
.device-link.active {
background: #5dade2 !important;
}
.btn-wide {
padding-left: 0;
padding-right: 0;
width: 100%;
}
.remote {
.remote,
.device {
margin: 0;
padding: 0;
position: relative;
}
.remotes-nav li,
.devices-nav li,
.macros-nav li {
margin: 20px 0;
}
Expand All @@ -117,10 +122,12 @@ a:active,
#container {
margin: 0 40px;
}
.remote {
.remote,
.device {
display: none;
}
.remote.active {
.remote.active,
.device.active {
display: block;
}
.hidden {
Expand Down
2 changes: 1 addition & 1 deletion static/css/compiled/compiled.css

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion static/js/app/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,34 @@ $(function() {
$('.back').on('click', function(evt) {
$('.remote.active').removeClass('active');
$('.remotes-nav').removeClass('hidden');
$('.devices-nav').removeClass('hidden');
$('.macros-nav').removeClass('hidden');
$('.back').addClass('hidden');
$('hr').removeClass('hidden');
$('#title').html($('#title').attr('data-text'));
$('#titlebar').removeClass('is-remote');
});

// Navigate to remote pages
$('.remotes-nav a').on('click', function(evt) {
$('.remotes-nav a, .devices-nav a').on('click', function(evt) {
evt.preventDefault();
var href = $(this).attr('href');
$('.remotes-nav').addClass('hidden');
$('.devices-nav').addClass('hidden');
$('.macros-nav').addClass('hidden');
$(href).addClass('active');
$('.back').removeClass('hidden');
$('hr').addClass('hidden');
$('#title').html($('#title').attr('data-text'));
$('#titlebar').removeClass('is-remote');
});

// Navigate to remote pages
$('.remotes-nav a, .devices-nav a').on('click', function(evt) {
evt.preventDefault();
var href = $(this).attr('href');
$('.remotes-nav').addClass('hidden');
$('.devices-nav').addClass('hidden');
$('#title').html($(this).html());
$('#titlebar').addClass('is-remote');
});
Expand Down
2 changes: 1 addition & 1 deletion static/js/compiled/app.js

Large diffs are not rendered by default.

37 changes: 36 additions & 1 deletion templates/index.swig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@

<hr />

<ul class="devices-nav">
{% for device in devices %}
{% set deviceName = device.name %}
<li><a class="btn btn-wide btn-large btn-inverse" href="#{{ deviceName|url_encode }}">{{ deviceName }}</a></li>
{% endfor %}
</ul>

<hr />

<ul class="remotes-nav">
<li><button class="btn btn-wide btn-large btn-inverse command-once" href="/zwave/2/on">Light On</button></li>
<li><button class="btn btn-wide btn-large btn-inverse command-once" href="/zwave/2/off">Light Off</button></li>
</ul>

<hr />

<ul class="macros-nav">
{% for macro in macros %}
{% set macroName = loop.key %}
Expand All @@ -45,13 +61,32 @@
<ul class="commands">
{% for command in remote %}
<li class="command">
<button class="command-link {% if repeaters[remoteName] && repeaters[remoteName][command] %}command-repeater{% else %}command-once{% endif %} btn btn-wide btn-large btn-primary" href="/remotes/{{ remoteName|url_encode }}/{{ command|url_encode }}">{{ labelForCommand(remoteName, command) }}</button>
<button class="remote-link command-link {% if repeaters[remoteName] && repeaters[remoteName][command] %}command-repeater{% else %}command-once{% endif %} btn btn-wide btn-large btn-primary" href="/remotes/{{ remoteName|url_encode }}/{{ command|url_encode }}">{{ labelForCommand(remoteName, command) }}</button>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>

<hr />

<ul class="devices">
{% for device in devices %}
{% set deviceName = device.name %}
<li class="remote" id="{{ deviceName|url_encode }}">
<ul class="commands">
{% for command in device.commands %}
{% set commandName = loop.key %}
<li class="command">
<button class="device-link command-link {% if repeaters[remoteName] && repeaters[remoteName][command] %}command-repeater{% else %}command-once{% endif %} btn btn-wide btn-large btn-primary" href="/devices/{{ deviceName }}/{{ commandName|url_encode }}">{{ commandName }}</button>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>

</div>

<div class="next"></div>
Expand Down
Loading