aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2021-12-22 15:02:39 +0000
committerdakkar <dakkar@thenautilus.net>2021-12-22 15:02:39 +0000
commitf673ed7ed761635e5efe3bc393c9299a35e0a1a7 (patch)
tree46ffebf42880711faf2b5b9f5359b1a2a2daacdd
parentnicer error handling (diff)
downloadmedia-control-f673ed7ed761635e5efe3bc393c9299a35e0a1a7.tar.gz
media-control-f673ed7ed761635e5efe3bc393c9299a35e0a1a7.tar.bz2
media-control-f673ed7ed761635e5efe3bc393c9299a35e0a1a7.zip
intergrated IR control
* PWA files * lirc wrapper * lircd config files
-rw-r--r--lib/App/MediaControl.rakumod27
-rw-r--r--lib/Lirc/Commands.rakumod54
-rw-r--r--lircd.conf.d/amplifier.conf35
-rw-r--r--lircd.conf.d/bluray.conf208
-rw-r--r--lircd.conf.d/projector.conf58
-rw-r--r--media-control.raku4
-rw-r--r--resources/index.html181
-rw-r--r--resources/ir.pngbin0 -> 106247 bytes
-rw-r--r--resources/ir.webmanifest15
9 files changed, 564 insertions, 18 deletions
diff --git a/lib/App/MediaControl.rakumod b/lib/App/MediaControl.rakumod
index 238ae53..e4b5024 100644
--- a/lib/App/MediaControl.rakumod
+++ b/lib/App/MediaControl.rakumod
@@ -2,20 +2,16 @@ use v6.d;
use Cro::HTTP::Server;
use Cro::HTTP::Router;
use Vlc::Client;
-use Lirc::Client;
+use Lirc::Commands;
class App::MediaControl {
has Vlc::Client $.vlc is required;
- has Lirc::Client $.lirc is required;
+ has Lirc::Commands $.lirc is required;
has Int $.port = 8080;
has Cro::Service $!service handles <stop>;
method start() {
- my $application = route {
- resources-from %?RESOURCES;
-
- get -> { resource 'index.html' }
-
+ my $vlc = route {
post -> 'play' { await self.vlc.command('pl_play') }
post -> 'pause' { await self.vlc.command('pl_pause') }
post -> 'stop' { await self.vlc.command('pl_stop') }
@@ -24,11 +20,28 @@ class App::MediaControl {
my $status = await self.vlc.status();
content 'application/json', $status;
}
+ }
+
+ my $ir = route {
+ post -> $thing, $arg {
+ await self.lirc.send($thing, $arg);
+ }
+ }
+
+ my $application = route {
+ resources-from %?RESOURCES;
+
+ get -> { resource 'index.html' }
+ get -> 'ir.png' { resource 'ir.png' }
+ get -> 'ir.webmanifest' { resource 'ir.webmanifest' }
+
+ include :$vlc, :$ir;
around -> &handler {
handler();
CATCH {
default {
+ note "BOOM $_";
response.status = 500;
content 'application/json', %( error => "$_" );
}
diff --git a/lib/Lirc/Commands.rakumod b/lib/Lirc/Commands.rakumod
new file mode 100644
index 0000000..a274bdc
--- /dev/null
+++ b/lib/Lirc/Commands.rakumod
@@ -0,0 +1,54 @@
+use v6.d;
+use Lirc::Client;
+
+class Lirc::Commands {
+ has Lirc::Client $.client is required;
+
+ sub hdmi-seq($input) {
+ return [
+ <amplifier power>,
+ <projector power>,
+ ('amplifier', "hdmi{$input}"),
+ ]
+ }
+ sub stop-seq() {
+ return [
+ <projector suspend>,
+ <amplifier hdmi1>,
+ <amplifier power>,
+ ];
+ }
+ my %sequences = (
+ 'start bluray' => [
+ <amplifier power>,
+ <amplifier hdmi2>,
+ <projector power>,
+ <bluray power>,
+ ],
+ 'stop bluray' => [
+ |stop-seq(),
+ <bluray power>,
+ ],
+ 'start hdmi1' => hdmi-seq(1),
+ 'start hdmi2' => hdmi-seq(2),
+ 'start hdmi3' => hdmi-seq(3),
+ 'stop hdmi' => stop-seq(),
+ );
+
+ method send($thing, $arg) {
+ if %sequences{"$thing $arg"} -> @seq {
+ for @seq -> @command {
+ await self.send(|@command);
+ sleep 1;
+ }
+ return Promise.kept();
+ }
+ else {
+ note "irsend $thing $arg";
+ return self.client.send(
+ remote => $thing,
+ keysym => "KEY_{$arg.uc}",
+ );
+ }
+ }
+}
diff --git a/lircd.conf.d/amplifier.conf b/lircd.conf.d/amplifier.conf
new file mode 100644
index 0000000..85b8cdb
--- /dev/null
+++ b/lircd.conf.d/amplifier.conf
@@ -0,0 +1,35 @@
+begin remote
+
+ name amplifier
+ bits 32
+ flags SPACE_ENC|CONST_LENGTH
+ eps 30
+ aeps 100
+
+ header 8903 4489
+ one 558 1646
+ zero 558 546
+ ptrail 560
+ repeat 8901 2256
+ gap 107251
+ toggle_bit_mask 0x0
+
+ begin codes
+ KEY_VOLUMEDOWN 0x5EA1D827
+ KEY_VOLUMEUP 0x5EA158A7
+ KEY_POWER 0x7E8154AB
+ KEY_HDMI1 0x5EA1E21C
+ KEY_HDMI2 0x5EA152AC
+ KEY_HDMI3 0x5EA1B24C
+ KEY_HDMI4 0x5EA10AF4
+ KEY_AV1 0x5EA1CA34
+ KEY_AV2 0x5EA16A94
+ KEY_AV3 0x5EA19A64
+ KEY_AV4 0x5EA13AC4
+ KEY_AV5 0x5EA1FA04
+ KEY_AUDIO1 0x5EA1A658
+ KEY_AUDIO2 0x5EA116E8
+ KEY_VAUX 0x5EA1AA55
+ end codes
+
+end remote
diff --git a/lircd.conf.d/bluray.conf b/lircd.conf.d/bluray.conf
new file mode 100644
index 0000000..5478194
--- /dev/null
+++ b/lircd.conf.d/bluray.conf
@@ -0,0 +1,208 @@
+begin remote
+ name bluray
+ flags RAW_CODES
+ eps 30
+ aeps 100
+ frequency 40000
+ min_repeat 3
+ gap 12000
+ begin raw_codes
+
+ name KEY_UP
+ 2400 600 1200 600 600 600 600 600
+ 1200 600 1200 600 1200 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_MENU
+ 2400 600 600 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_CONTEXT_MENU
+ 2400 600 1200 600 600 600 600 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_LEFT
+ 2400 600 1200 600 1200 600 600 600
+ 1200 600 1200 600 1200 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_OK
+ 2400 600 1200 600 600 600 1200 600
+ 1200 600 1200 600 1200 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_RIGHT
+ 2400 600 600 600 600 600 1200 600
+ 1200 600 1200 600 1200 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name BTN_BACK
+ 2400 600 1200 600 1200 600 600 600
+ 600 600 600 600 600 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_DOWN
+ 2400 600 600 600 1200 600 600 600
+ 1200 600 1200 600 1200 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_OPTION
+ 2400 600 1200 600 1200 600 1200 600
+ 1200 600 1200 600 1200 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_RED
+ 2400 600 1200 600 1200 600 1200 600
+ 600 600 600 600 1200 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_GREEN
+ 2400 600 600 600 600 600 600 600
+ 1200 600 600 600 1200 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_YELLOW
+ 2400 600 1200 600 600 600 600 600
+ 1200 600 600 600 1200 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_BLUE
+ 2400 600 600 600 1200 600 1200 600
+ 600 600 600 600 1200 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_HOME
+ 2400 600 600 600 1200 600 600 600
+ 600 600 600 600 600 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_S
+ 2400 600 600 600 600 600 1200 600
+ 1200 600 600 600 600 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_N
+ 2400 600 1200 600 1200 600 600 600
+ 1200 600 600 600 600 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_PREVIOUS
+ 2400 600 1200 600 1200 600 600 600
+ 1200 600 1200 600 600 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_PLAY
+ 2400 600 600 600 1200 600 600 600
+ 1200 600 1200 600 600 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_NEXT
+ 2400 600 600 600 600 600 1200 600
+ 1200 600 1200 600 600 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_REWIND
+ 2400 600 1200 600 1200 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_PAUSE
+ 2400 600 1200 600 600 600 600 600
+ 1200 600 1200 600 600 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_STOP
+ 2400 600 600 600 600 600 600 600
+ 1200 600 1200 600 600 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_FASTFORWARD
+ 2400 600 600 600 1200 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_AUDIO
+ 2400 600 600 600 600 600 1200 600
+ 600 600 600 600 1200 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_DISPLAYTOGGLE
+ 2400 600 1200 600 600 600 600 600
+ 600 600 600 600 600 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_SUBTITLE
+ 2400 600 1200 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_EJECTCD
+ 2400 600 600 600 1200 600 1200 600
+ 600 600 1200 600 600 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ name KEY_POWER
+ 2400 600 1200 600 600 600 1200 600
+ 600 600 1200 600 600 600 600 600
+ 600 600 1200 600 600 600 1200 600
+ 1200 600 600 600 1200 600 600 600
+ 600 600 600 600 1200 600 1200 600
+ 1200
+ end raw_codes
+end remote \ No newline at end of file
diff --git a/lircd.conf.d/projector.conf b/lircd.conf.d/projector.conf
new file mode 100644
index 0000000..f681c98
--- /dev/null
+++ b/lircd.conf.d/projector.conf
@@ -0,0 +1,58 @@
+begin remote
+ name projector
+
+ bits 16
+ flags SPACE_ENC|CONST_LENGTH
+ eps 30
+ aeps 100
+
+ header 9043 4415
+ one 623 1604
+ zero 623 504
+ ptrail 623
+ pre_data_bits 16
+ pre_data 0xC1AA
+ gap 107401
+ toggle_bit_mask 0x0
+
+ begin codes
+ KEY_POWER 0x09F6
+ KEY_SUSPEND 0x8976 # Standby
+ KEY_PLAY 0x7A85
+ KEY_PAUSE 0xDA25
+ KEY_STOP 0x9A65
+ KEY_MUTE 0xB54A
+ KEY_VOLUMEUP 0x19E6
+ KEY_VOLUMEDOWN 0x9966
+ KEY_FORWARD 0xFA05 # >>|
+ KEY_BACK 0xBA45 # |<<
+ KEY_FRAMEFORWARD 0x3AC5 # >>
+ KEY_FRAMEBACK 0x5AA5 # <<
+ KEY_ESC 0x21DE
+ KEY_MENU 0x59A6
+ KEY_F1 0x06F9 # Link Menu
+ KEY_F2 0x6A95 # HDMI Link
+ KEY_F3 0x9C63 # Default
+ KEY_UP 0x0DF2
+ KEY_DOWN 0x4DB2
+ KEY_LEFT 0xCD32
+ KEY_RIGHT 0x8D72
+ KEY_F10 0xCE31 # HDMI1
+ KEY_F11 0xEE11 # HDMI2
+ KEY_F12 0x1EE1 # WirelessHD
+ KEY_F13 0x6E91 # USB
+ KEY_F14 0x8E71 # Component
+ KEY_F15 0x0EF1 # Video
+ KEY_F16 0xB946 # PC
+ KEY_F4 0xC43B # 2D/3D
+ KEY_F5 0xF10E # Colour Mode
+ KEY_F6 0xD12E # Memory
+ KEY_F7 0xA45B # Auto Iris
+ KEY_F8 0xBC43 # RGBCMY
+ KEY_F9 0x51AE # Aspect
+ KEY_F17 0x41BE # Split
+ KEY_F18 0x6996 # Pattern
+ KEY_F19 0xF906 # User
+ end codes
+
+end remote
diff --git a/media-control.raku b/media-control.raku
index 38517eb..879a11f 100644
--- a/media-control.raku
+++ b/media-control.raku
@@ -4,6 +4,7 @@ use lib 'inst#local','file#lib';
use Config::TOML;
use Vlc::Client;
use Lirc::Client;
+use Lirc::Commands;
use App::MediaControl;
my $config = from-toml(file=>'config.toml');
@@ -13,7 +14,8 @@ my Vlc::Client $vlc .= new(
base-uri => $config<vlc><base-uri>,
);
-my Lirc::Client $lirc .= new();
+my Lirc::Client $lirc-client .= new();
+my Lirc::Commands $lirc .= new(client=>$lirc-client);
my App::MediaControl $app .= new(
port => $config<server><port>,
diff --git a/resources/index.html b/resources/index.html
index e33ac9a..cbf0a75 100644
--- a/resources/index.html
+++ b/resources/index.html
@@ -2,11 +2,12 @@
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
- <title>vlc</title>
+ <link rel="manifest" href="ir.webmanifest">
+ <link rel="icon" href="ir.png">
+ <title>IR</title>
<script>
"use strict"
-
- function vlcCall(method,path,args) {
+ function call(method,path,args) {
args ||= {};
let url=new URL(path,location.href);
for (let a in args) {
@@ -19,11 +20,15 @@
}
function vlcCommand(command,args) {
- vlcCall('post',command,args);
+ call('post',`vlc/${command}`,args);
}
+ function irCommand(thing,arg) {
+ call('post',`ir/${thing}/${arg}`);
+ }
+
async function updateView() {
- const status = (await (await vlcCall('get','status')).json()).status;
+ const status = (await (await call('get','vlc/status')).json()).status;
const currentPos = document.querySelector('#current-pos');
if (status.state != 'playing') {
@@ -47,12 +52,168 @@
}
})
</script>
+ <style>
+ body { font-size: 12vw }
+ * { font-size: inherit }
+
+ div.thing {
+ padding: 0.2em 0.1em 0.2em 0.1em;
+ margin-top: 0.5em;
+ margin-bottom: 1em;
+ border: solid 0.05em black;
+ border-radius: 0.5em;
+ position: relative;
+ }
+ div.thing label {
+ font-size: 0.8em;
+ line-height: 0.5em;
+ position: absolute;
+ top: -0.5em;
+ left: 1em;
+ background-color: white;
+ }
+ div.thing table {
+ width: 100%;
+ table-layout: fixed;
+ }
+ td button {
+ border-radius: 0.5em;
+ width: calc(100% - 0.2em);
+ margin: 0.1em;
+ }
+
+ .power { color: white }
+ .power.on { background-color: green }
+ .power.off { background-color: red }
+
+ .volume { background-color: lightblue }
+ .input { background-color: darkorange }
+ .control { background-color: black; color: white; filter: grayscale(100%) }
+ .bd-red { background-color: red }
+ .bd-yellow { background-color: yellow }
+ .bd-blue { background-color: blue; color: white }
+ </style>
</head>
<body>
- <button onclick="vlcCommand('play')">play</button>
- <button onclick="vlcCommand('pause')">pause</button>
- <button onclick="vlcCommand('stop')">stop</button>
- <button onclick="updateView()">update</button>
- <input type="range" id="current-pos">
+ <div class="thing">
+ <label>Vlc</label>
+ <table>
+ <tr>
+ <td><button onclick="irCommand('start','hdmi3')" class="power on">start</button></td>
+ <td><button onclick="irCommand('stop','hdmi')" class="power off">stop</button></td>
+ </tr>
+ </table>
+ <details>
+ <summary>controls</summary>
+ <button onclick="vlcCommand('play')">play</button>
+ <button onclick="vlcCommand('pause')">pause</button>
+ <button onclick="vlcCommand('stop')">stop</button>
+ <button onclick="updateView()">update</button>
+ <input type="range" id="current-pos">
+ </details>
+ </div>
+ <div class="thing">
+ <label>Laptop</label>
+ <table>
+ <tr>
+ <td><button onclick="irCommand('start','hdmi1')" class="power on">start</button></td>
+ <td><button onclick="irCommand('stop','hdmi')" class="power off">stop</button></td>
+ </tr>
+ </table>
+ </div>
+ <div class="thing">
+ <label>Bluray</label>
+ <table>
+ <tr>
+ <td><button onclick="irCommand('start','bluray')" class="power on">start</button></td>
+ <td><button onclick="irCommand('stop','bluray')" class="power off">stop</button></td>
+ </tr>
+ </table>
+ <details>
+ <summary>controls</summary>
+ <table>
+ <tr>
+ <td><button onclick="irCommand('bluray','ejectcd')" class="input">⏏</button></td>
+ <td>&nbsp;</td>
+ <td><button onclick="irCommand('bluray','power')" class="power on">ON</button></td>
+ </tr>
+ <tr>
+ <td><button onclick="irCommand('bluray','yellow')" class="bd-yellow">a</button></td>
+ <td><button onclick="irCommand('bluray','blue')" class="bd-blue">b</button></td>
+ <td><button onclick="irCommand('bluray','red')" class="bd-red">c</button></td>
+ </tr>
+ <tr>
+ <td><button onclick="irCommand('bluray','menu')" class="control">🔝</button></td>
+ <td><button onclick="irCommand('bluray','up')" class="control">↑</button></td>
+ <td><button onclick="irCommand('bluray','context_menu')" class="control">⎙</button></td>
+ </tr>
+ <tr>
+ <td><button onclick="irCommand('bluray','left')" class="control">←</button></td>
+ <td><button onclick="irCommand('bluray','ok')" class="control">ok</button></td>
+ <td><button onclick="irCommand('bluray','right')" class="control">→</button></td>
+ </tr>
+ <tr>
+ <td><button onclick="irCommand('bluray','back')" class="control">🔙</button></td>
+ <td><button onclick="irCommand('bluray','down')" class="control">↓</button></td>
+ <td><button onclick="irCommand('bluray','option')" class="control">opt</button></td>
+ </tr>
+ <tr>
+ <td colspan="3"><button onclick="irCommand('bluray','home')" class="control">home</button></td>
+ </tr>
+
+ <tr>
+ <td><button onclick="irCommand('bluray','previous')" class="control">⏪</button></td>
+ <td><button onclick="irCommand('bluray','play')" class="control">▶</button></td>
+ <td><button onclick="irCommand('bluray','next')" class="control">⏩</button></td>
+ </tr>
+ <tr>
+ <td><button onclick="irCommand('bluray','review')" class="control">⏮</button></td>
+ <td><button onclick="irCommand('bluray','pause')" class="control">⏸</button></td>
+ <td><button onclick="irCommand('bluray','fastforward')" class="control">⏭</button></td>
+ </tr>
+ <tr>
+ <td><button onclick="irCommand('bluray','subtitle')" class="control">sub</button></td>
+ <td><button onclick="irCommand('bluray','stop')" class="control">⏹</button></td>
+ <td><button onclick="irCommand('bluray','audio')" class="control">aud</button></td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td><button onclick="irCommand('bluray','displaytoggle')" class="control">disp</button></td>
+ </tr>
+ </table>
+ </details>
+ </div>
+ <div class="thing">
+ <label>Amp</label>
+ <table>
+ <tr>
+ <td><button onclick="irCommand('amplifier','power')" class="power on">ON</button></td>
+ <td><button onclick="irCommand('amplifier','power')" class="power off">OFF</button></td>
+ <td><button onclick="irCommand('amplifier','volumeup')" class="volume">↑</button></td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td>&nbsp;</td>
+ <td><button onclick="irCommand('amplifier','volumedown')" class="volume">↓</button></td>
+ </tr>
+ <tr>
+ <td><button onclick="irCommand('amplifier','hdmi1')" class="input">Lap</button></td>
+ <td><button onclick="irCommand('amplifier','hdmi2')" class="input">BD</button></td>
+ <td><button onclick="irCommand('amplifier','hdmi3')" class="input">NAS</button></td>
+ </tr>
+ </table>
+ </div>
+
+ <div class="thing">
+ <label>Projector</label>
+ <table>
+ <tr>
+ <td><button onclick="irCommand('projector','power')" class="power on">ON</button></td>
+ <td><button onclick="irCommand('projector','suspend')" class="power off">OFF</button></td>
+ </tr>
+ </table>
+ </div>
+
</body>
</html>
diff --git a/resources/ir.png b/resources/ir.png
new file mode 100644
index 0000000..43b1ebe
--- /dev/null
+++ b/resources/ir.png
Binary files differ
diff --git a/resources/ir.webmanifest b/resources/ir.webmanifest
new file mode 100644
index 0000000..256b7ce
--- /dev/null
+++ b/resources/ir.webmanifest
@@ -0,0 +1,15 @@
+{
+ "background_color": "white",
+ "description": "Living room infrared control",
+ "display": "standalone",
+ "icons": [
+ {
+ "src": "ir.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ }
+ ],
+ "name": "Infrared control",
+ "short_name": "IR",
+ "start_url": "/ir/"
+}