#!/usr/bin/perl
# Originally from bradfitz (denon.pl on github).
use strict;
use IO::Socket::INET;
use Time::HiRes qw (sleep);
use POSIX qw(mkfifo);
use Carp qw(croak);
use FileHandle;
my $host = "denon";
my $fifo = "/var/run/denon.fifo";
my $port = 23;
STDOUT->autoflush(1);
STDERR->autoflush(1);
("\xff\xfd\x03" eq fromhex("ff fd", " 03 ")) or die "Unittest failed";
# ----------------
my $sock = IO::Socket::INET->new(PeerAddr => $host,
PeerPort => $port)
or die "Failed to connect to $host:$port";
sub expect_from_denon {
my $expected = shift;
my $got = "";
my $buf;
print "Waiting on ", printable($expected), "...";
while (length($got) < length($expected) &&
sysread($sock, $buf, length($expected) - length($got))) {
$got .= $buf;
}
croak "Didn't get expected input." unless $got eq $expected;
print "Got it.\n";
return 1;
}
sub fromhex {
my $in = join(', @_);
$in =~ s/\s*(..)\s*/chr(hex($1))/eg;
return $in;
}
sub send_to_denon {
my $str = shift;
syswrite($sock, $str) = length($str) or die;
# We don't sync with the reader, but blind sleep for each line sent.
sleep 0.2;
}
sub printable {
my $str = shift;
$str =~ s/[^[:print:]]/sprintf("x%02x", ord($&))/eg;
return $str;
}
sub turn_off_myth {
# for key in 'key space' 'jump mainmenu' 'key escape' 'key down' 'key enter'; do
# echo "$key" | nc myth 6546; echo "$key"; sleep 3; done
system("date; /var/local/scr/alarm 10 telnet myth 10221; date");
}
sub wakeup_activate_myth {
if (not system("fping -c3 -p1 -q myth"))
{
print "myth is already up, toggle X screen to wake X up\n";
system("date; /var/local/scr/alarm 20 telnet myth 10222; date");
}
else
{
# If we start myth too quickly and the display isn't ready, it becomes
# unable to talk to it until it goes through another suspend/resume.
# Mmmh, actually it doesn't look like sleeping here is useful. but let's
sleep 1;
print "myth is down, wake it up and restart X\n";
# 5 Seconds is a bit aggressive for the machine to wake up and
# be ready to flip Xorg, but it seems to work.
system("date; sudo /var/local/scr/myth-fmr_wol; fping -r 5 myth && date && "
"echo 'sleep 5 wait for X to restart safely' && sleep 5 && /var/local/scr/alarm 20 telnet myth 10222; date");
}
}
sub printlog {
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
my $mesg = $_[0];
$year+=1900;
$mon++;
chomp($mesg);
printf LOG ("%.4d/%.2d/%.2d %.2d:%.2d:%.2d - $mesg\n", $year, $mon, $mday, $hour, $min, $sec);
}
# --------------------------------------------------------------------------------
my $hello = fromhex("ff fd 03", # Do Suppress Go ahead
"ff fb 18", # Will Terminal Type
"ff fb 1f", # Will Negotiate About Window Size
"ff fb 20", # Will Terminal Speed
"ff fb 21", # Will Remote Flow Control
"ff fb 22", # Will Linemode
"ff fb 27", # Will New Environment Option
"ff fd 05", # Do Status
);
send_to_denon($hello);
expect_from_denon(fromhex("ff fb 03")); # Will Suppress Go Ahead
expect_from_denon(fromhex("ff fa 18 01 ff f0")); # Send your terminal type
print "send terminal.\n";
send_to_denon(fromhex("ff fa 18 00",
"rxvt",
"ff f0", # suboption end
));
expect_from_denon("BridgeCo AG Telnet server\x0a\x0d");
my $child = fork;
defined($child) or die "Fork failure.";
if ($child) # we're the parent process. accept input.
{
if ($#ARGV eq -1)
{
while (1)
{
print "DENON> ";
my $line = <STDIN>;
chomp $line;
next if (!$line);
exit if ($line eq "quit" or $line eq "q" or $line eq "exit");
send_to_denon($line . "\x0d");
}
}
elsif ($#ARGV eq 0)
{
my $arg = $ARGV[0];
if ($arg eq "--server")
{
-p $fifo or mkfifo($fifo, 0700) or die "mkfifo($fifo) failed: $!";
print "Will read from fifo $fifo until ^C\n";
my $logfile = "/var/log/denon-send.log";
open(LOG, ">>$logfile") or die "Can't write to $logfile";
LOG->autoflush(1);
open(FIFO, "<$fifo") or die "Can't read from $fifo: $!";
while (1)
{
sleep 0.1;
my $arg = <FIFO>;
next if (not $arg);
chomp $arg;
printlog("sending $arg");
send_to_denon($arg . "\x0d");
}
}
else
{
print "Will send $arg to $host\n";
send_to_denon($arg . "\x0d");
}
}
elsif ($#ARGV eq 1)
{
my ($arg, $repeat) = @ARGV;
print "Will send $arg to $host $repeat times\n";
foreach $_ (1 .. $repeat)
{
send_to_denon($ARGV[0] . "\x0d");
}
}
else
{
die "Too many args: ".join(" ", @ARGV);
}
}
else # Child process,
{
my $logfile = "/var/log/denon.log";
open(LOG, ">>$logfile") or die "Can't write to $logfile";
LOG->autoflush(1);
# Init state machine for keeping track of whether receiver is on or off
# and on which output. By default, we'll pretend the last output was DVR
my $input = "SIDVR";
# Sometimes, the connection to denon dies when denon starts up.
# When this happens, we missed the first PWON, so we assume that
# we're at stage one already (first PWON seen) when we start.
my $denon_state = "on_stage1";
# Turning back on to mythtv when it was off, we need to wait for the 2nd PWON
# 2011/11/09 05:50:17 - PSROOM EQ:AUDYSSEY
# 2011/11/09 05:50:17 - PWON
# 2011/11/09 05:50:17 - ZMON
# 2011/11/09 05:50:20 - PSROOM EQ:AUDYSSEY
# 2011/11/09 05:50:21 - PSROOM EQ:AUDYSSEY
# 2011/11/09 05:50:24 - PSROOM EQ:AUDYSSEY
# 2011/11/09 05:50:24 - PSROOM EQ:AUDYSSEY
# 2011/11/09 05:50:25 - PSROOM EQ:AUDYSSEY
# look for 2nd PWON vvvvvvvv
# 2011/11/09 05:50:25 - PWON
# 2011/11/09 05:50:42 - PSROOM EQ:AUDYSSEY
# 2011/11/09 05:50:44 - PSROOM EQ:AUDYSSEY
# 2011/11/09 05:50:44 - PSROOM EQ:AUDYSSEY
#
# Turning on when mythtv was the last input to DVD
# 2011/11/09 05:48:22 - ZMOFF
# 2011/11/09 05:48:38 - PSROOM EQ:AUDYSSEY
# 2011/11/09 05:48:38 - PWON
# 2011/11/09 05:48:39 - ZMON
# 2011/11/09 05:48:41 - PSROOM EQ:AUDYSSEY
# 2011/11/09 05:48:43 - SIDVD
# 2011/11/09 05:48:43 - Denon switched input to SIDVD
# 2011/11/09 05:48:43 - SDHDMI
# 2011/11/09 05:48:43 - SVSAT
# 2011/11/09 05:48:43 - DCAUTO
# 2011/11/09 05:48:43 - CVFL 495
# 2011/11/09 05:48:43 - CVFR 505
# 2011/11/09 05:48:43 - CVC 495
# 2011/11/09 05:48:43 - CVSW 52
# 2011/11/09 05:48:43 - CVSL 495
# 2011/11/09 05:48:43 - CVSR 52
# 2011/11/09 05:48:43 - CVSBL 50
# 2011/11/09 05:48:43 - CVSBR 50
# 2011/11/09 05:48:43 - CVSB 50
# 2011/11/09 05:48:43 - MVMAX 98
# 2011/11/09 05:48:43 - PSROOM EQ:AUDYSSEY
# look for 2nd PWON vvvvvvvv
# 2011/11/09 05:48:47 - PWON
# 2011/11/09 05:49:19 - SIDVR
# 2011/11/09 05:49:19 - SDAUTO
# 2011/11/09 05:49:19 - SVDVR
# 2011/11/09 05:49:19 - DCAUTO
# Show any read data, and depending on some commands read, wake up
# or put mythtv to sleep.
my $data;
while (sysread($sock, $data, 300))
{
$data =~ s/\015/\n/g;
foreach my $line (split(/\n/, $data))
{
printlog("Read: ".printable($line));
if ($line =~ /SIDVR/)
{
printlog("Denon switched to MythTV (denon state $denon_state), waking up myth");
wakeup_activate_myth();
$input = $line;
}
elsif ($line =~ /^SI/)
{
printlog("Denon switched input to $line");
$input = $line;
if ($denon_state eq "on")
{
printlog("Denon on (state $denon_state), but switched to $input. Turn off mythtv");
turn_off_myth();
}
}
elsif ($line =~ /PWON/)
{
if ($denon_state eq "off")
{
$denon_state = "on_stage1";
printlog("Denon being turned on, switching to stage1 on");
# Between now and the second 'ON', we'll have gotten a new
# input line if input got switched at power on, or we use
# the last one we've seen in the past.
}
else
{
printlog("Got another PWON (was $denon_state)");
$denon_state = "on";
if ($input =~ /SIDVR/)
{
printlog("Denon turned on and was last on MythTV, waking up myth");
wakeup_activate_myth();
}
else
{
printlog("Denon turned on but input is $input, doing nothing");
}
}
}
elsif ($line =~ /PWSTANDBY/)
{
$denon_state = "off";
if ($input =~ /SIDVR/)
{
printlog("Denon turned off and was last on MythTV, turning off");
turn_off_myth();
}
else
{
printlog("Denon turned off but last input was $input, not doing anything");
}
}
}
}
}
sub END
{
#print "Killing child\n";
kill(9, $child);
}
exit;
___END___
#!/bin/bash
# $0 cmd [repeat]
for i in `seq 1 ${2-1}`
do
echo -ne "$1\r" | nc -q0 denon 23
sleep 0.2
done
exit
PWON", "xon"
PWSTANDBY
PW?", "power-status"
MVUP", "volplus"
MVDOWN", "volminus"
MV62", "vol-18"
MV80", "vol+0"
MV98", "vol+18"
MUON", "mute-on"
MUOFF", "mute-off"
SIPHONO", "input-phono"
SICD", "input-cd"
SITUNER", "input-tuner"
SIDVD", "input-dvd"
SIVDP", "input-vdp"
SITV", "input-tv"
SIDBS", "input-dbs"
SIVCR-1", "input-vcr1"
SIVCR-2", "input-vcr2"
SIV.AUX", "input-vaux"
SICDR/TAPE", "input-cdr"
SI?", "input-status"
SDAUTO", "digital-in-auto"
SDPCM", "digital-in-pcm"
SDDTS", "digital-in-dts"
SDANALOG", "digital-in-analog"
SDEXT.IN-1", "digital-in-ext-in"
SVDVD", "video-select-dvd"
SVVDP", "video-select-vdp"
SVTV", "video-select-tv"
SVDBS", "video-select-dbs"
SVVCR-1", "video-select-vcr1"
SVVCR-2", "video-select-vcr2"
SVV.AUX", "video-select-vaux"
SVSOURCE", "video-select-source"
MSDIRECT", "surround-mode-direct"
MSPURE DIRECT", "surround-mode-pure-direct"
MSSTEREO", "surround-mode-stereo"
MSMULTI CH IN", "surround-mode-multi-ch-in"
MSMULTI CH DIRECT", "surround-mode-multi-ch-direct"
MSMULTI CH PURE D", "surround-mode-multi-ch-pure-direct"
MSDOLBY PRO LOGIC", "surround-mode-dolby-pro-logic"
MSDOLBY PL2", "surround-mode-dolby-pl2"
MSDOLBY PL2x", "surround-mode-dolby-pl2x"
MSDOLBY DIGITAL", "surround-mode-dolby-digital"
MSDOLBY D EX", "surround-mode-dolby-d-ex"
MSDTS NEO:6", "surround-mode-dts-neo6"
MSDTS SURROUND", "surround-mode-dts-surround"
MSDTS ES DSCRT6.1", "surround-mode-dts-es-dscrt61"
MSDTS ES MTRX6.1", "surround-mode-dts-es-mtrx61"
MSWIDE SCREEN", "surround-mode-wide-screen"
MS5CH STEREO", "surround-mode-5ch-stereo"
MS7CH STEREO", "surround-mode-7ch-stereo"
MSSUPER STADIUM", "surround-mode-super-stadium"
MSROCK ARENA", "surround-mode-rock-arena"
MSJAZZ CLUB", "surround-mode-jazz-club"
MSCLASSIC CONCERT", "surround-mode-classic-concert"
MSMONO MOVIE", "surround-mode-mono-movie"
MSMATRIX", "surround-mode-matrix"
MSVIDEO GAME", "surround-mode-video-game"
MSVIRTUAL", "surround-mode-virtual"
MS?", "surround-mode-status"
MSUSER1", "surround-mode-user1"
MSUSER2", "surround-mode-user2"
MSUSER3", "surround-mode-user3"
SSCUP", "cursor-up"
SSCDN", "cursor-down"
SSCLT", "cursor-left"
SSCRT", "cursor-right"
SSENT", "cursor-enter"
SSMEN ON", "system-setup-menu-on"
SSMEN OFF", "system-setup-menu-off"
PSMEN ON", "surround-parameter-menu-on"
PSMEN OFF", "surround-parameter-menu-off"