I can never find the TV remote.
Whether it’s slipped somewhere in or under the sofa, or been hidden by the kids, I’m often at a loss to find it.
A mobile phone, on the other hand, is rarely too far from my hand 🙂
So I’ve hacked together a quick app to put a TV remote control interface on my phone. It’s definitely a quick-and-dirty chunk of code, but it does now mean that I can work my TV from my mobile if I can’t find the real remote.
On the off-chance that this is useful to other vdr users, I wanted to share how I did it.
I went for a web app rather than native code.
This was mainly because I’m switching a lot between Android, Palm’s webOS and Windows Mobile at the moment, and didn’t really want to have to write three separate applications! A web app can be hosted on the same home server that runs vdr for my home TV, and accessed from any platform. It’s only hosting internally – so you have to be connected to my home wifi to access it. But as all my phones have wifi, and the app isn’t really that useful when I’m not at home, this isn’t a problem.
I’ve mentioned before that vdr listens on a TCP port for commands, and that the svdrpsend utility provides an easy way to send commands in the correct format.
To send a remote control command, you run:
svdrpsend HITK <NAME>
where <NAME>
is the name of a remote control key.
My web app just displays buttons on a web page. Pressing one of the buttons causes the relevant svdrpsend command to be run.
I have used a JavaScript XMLHttpRequest to save reloading the page after each press (useful when repeatedly tapping ‘Up’ or ‘Down’ to navigate menus). But apart from that, it’s all pretty straightforward.
It’s not as nice an interface as my real remote control. But it makes for a useful backup when I can’t find the real thing 🙂
The code is below.
I’m using cherrypy to host it (for no particular reason than I already had it installed from a previous project).
import cherrypy import os.path from subprocess import call class VDRRemote: def runcmd(self, cmd=None): if (cmd is not None): if cmd == "ONOFF": call(['/home/dale/scripts/toggle-tv.sh']) elif cmd == "VOLDOWN": call(['svdrpsend', 'HITK', 'Volume-']) elif cmd == "VOLUP": call(['svdrpsend', 'HITK', 'Volume+']) elif cmd == "CHANDOWN": call(['svdrpsend', 'HITK', 'Channel-']) elif cmd == "CHANUP": call(['svdrpsend', 'HITK', 'Channel+']) else: call(['svdrpsend', 'HITK', cmd]) def index(self): return ''' <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>vdr</title> <style type="text/css" media="screen">@import "images/remote.css";</style> <script> function invokeCmd(param) { var http = new XMLHttpRequest(); http.open("GET", "/runcmd?cmd=" + param, true); http.send(null); } </script> </head> <body> <div> <table class="btnTable" selected="true"> <tr> <td><a class="borderImageBtn blackButton" href="javascript:invokeCmd('ONOFF')">On/Off</a></td> <td> </td> <td><a class="borderImageBtn blackButton" href="javascript:invokeCmd('Mute')">Mute</a></td> </tr> <tr> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('1')">1</a></td> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('2')">2</a></td> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('3')">3</a></td> </tr> <tr> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('4')">4</a></td> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('5')">5</a></td> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('6')">6</a></td> </tr> <tr> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('7')">7</a></td> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('8')">8</a></td> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('9')">9</a></td> </tr> <tr> <td> </td> <td><a class="borderImageBtn blueButton" href="javascript:invokeCmd('0')">0</a></td> <td> </td> </tr> <tr> <td colspan="3"> </td> </tr> <tr> <td> </td> <td><a class="borderImageBtn whiteButton" href="javascript:invokeCmd('Up')">↑</a></td> <td> </td> </tr> <tr> <td><a class="borderImageBtn whiteButton" href="javascript:invokeCmd('Left')">←</a></td> <td><a class="borderImageBtn whiteButton" href="javascript:invokeCmd('Ok')">OK</a></td> <td><a class="borderImageBtn whiteButton" href="javascript:invokeCmd('Right')">→</a></td> </tr> <tr> <td> </td> <td><a class="borderImageBtn whiteButton" href="javascript:invokeCmd('Down');">↓</a></td> <td> </td> </tr> </table> <table class="btnTable" selected="true"> <tr> <td><a class="borderImageNarrowBtn blackButton" href="javascript:invokeCmd('MENU')">Menu</a></td> <td><a class="borderImageNarrowBtn blackButton" href="javascript:invokeCmd('Recordings')">Rec TV</a></td> <td><a class="borderImageNarrowBtn blackButton" href="javascript:invokeCmd('Info')">Info</a></td> <td><a class="borderImageNarrowBtn blackButton" href="javascript:invokeCmd('Back')">Back</a></td> </tr> <tr> <td><a class="borderImageNarrowBtn blueButton" href="javascript:invokeCmd('Record')">Record</a></td> <td><a class="borderImageNarrowBtn blueButton" href="javascript:invokeCmd('Play')">Play</a></td> <td><a class="borderImageNarrowBtn blueButton" href="javascript:invokeCmd('Pause')">Pause</a></td> <td><a class="borderImageNarrowBtn blueButton" href="javascript:invokeCmd('Stop')">Stop</a></td> </tr> <tr> <td><a class="borderImageNarrowBtn blackLeftButton" href="javascript:invokeCmd('VOLDOWN')">Vol −</a></td> <td><a class="borderImageNarrowBtn blackRightButton" href="javascript:invokeCmd('VOLUP')">Vol +</a></td> <td><a class="borderImageNarrowBtn blackLeftButton" href="javascript:invokeCmd('CHANDOWN')">Chan −</a></td> <td><a class="borderImageNarrowBtn blackRightButton" href="javascript:invokeCmd('CHANUP')">Chan +</a></td> </tr> <tr> <td><a class="borderImageNarrowBtn blackLeftButton" href="javascript:invokeCmd('Green')">60 sec</a></td> <td><a class="borderImageNarrowBtn blackLeftButton" href="javascript:invokeCmd('FastRew')">Rewind</a></td> <td><a class="borderImageNarrowBtn blackRightButton" href="javascript:invokeCmd('FastFwd')">FFwd</a></td> <td><a class="borderImageNarrowBtn blackRightButton" href="javascript:invokeCmd('Yellow')">60 sec</a></td> </tr> <tr> <td><a class="borderImageNarrowBtn redButton" href="javascript:invokeCmd('Red')"> </a></td> <td><a class="borderImageNarrowBtn greenButton" href="javascript:invokeCmd('Green')"> </a></td> <td><a class="borderImageNarrowBtn yellowButton" href="javascript:invokeCmd('Yellow')"> </a></td> <td><a class="borderImageNarrowBtn blueButton" href="javascript:invokeCmd('Blue')"> </a></td> </tr> </table> </div> </body> </html>''' index.exposed = True runcmd.exposed = True cherrypy.server.socket_host="0.0.0.0" current_dir = os.path.dirname(os.path.abspath(__file__)) conf = {'/images': {'tools.staticdir.on' : True, 'tools.staticdir.dir' : os.path.join(current_dir, 'images')}} cherrypy.quickstart(VDRRemote(), config=conf)
this is cool and is particularly useful for when you don’t have line of sight to the main backend, and your frontend device doesn’t have a remote (e.g., my Thinkpad)
my only comment is that you could actually package this up as a VDR plugin so that it runs within the main VDR process and calls the native channel-switch etc. commands like the ‘Remote’ page on VDR-live does.
One enhancement that would be pretty cool would be to have a context switch that changes the labeling of the buttons.
e.g., the numerical buttons when you’re playing back a recording actually start do cutting actions (add a cutpoint, move to next cutpoint, start cutting etc.) so it’d be quite cool to display these names when playing back.
The reference card at http://www.ramge.de/olaf/vdr/ is quite useful to discover these 🙂
Thanks very much for the link – I didn’t know about all those commands.
Context-sensitive remote would be a good idea. Even simple things like disabling the ‘Record’ button when you’re watching a recorded programme would be a good start. Something to look into if I come up with a version 0.2 🙂
I was a bit too lazy to look into how to write a VDR plugin, but I like the idea. Do you know of any simple examples?
I’ll have a look around.
Btw iVDR might have some perl you can ‘borrow’ to add stuff like EPG parsing 🙂
http://l-b-c.net/iVDR/pics.htm
Hi,
this looks great. I have a Palm Pre too and I am looking for an App for VDR-Remote. A Plugin for VDR would be great!!!
Marcel
Hi,
my first little webOS-App is ready and online. Maybe you are interested in it. VDRemote – a remote control for webOS:
http://developer.palm.com/webChannel/index.php?packageid=com.r11gs-de.vdremote#
http://webos.r11gs.de
LinuxQ
You star – that’s great news!
I’m away at the moment, but will definitely be giving this a try when I get home at the weekend.
Thanks very much for letting me know.
Kind regards,
D
LinuxQ and all… I have come across a VDRemote for webOS thru Preware… but I’m not exactly sure what constitutes a “VDR”.
Are you telling me that I just setup my MythTV backend and then your app will auto-connect to it??
Because that would be sweet! 🙂