|
π
2013-11-16 01:01
in Linux, Linuxha
Recent Denon AV Receivers, like at least the 3806, 3807, 3808, 3809, 3810, 3811, 3812, 3813, as well as 4806, 4807, 4808, 4809, 4810, 4811, 4812, 4813 have a TCP/IP connection you can use to control the receiver via telnet.
But the important thing is that the telnet connection 2-way, so it will also tell you whathe receiver is doing. I use this to know when the receiver turns on or off, and more importantly when it tunes into my mythtv input. When that happens, I have my controlling PC (the one that's always on) know that the AV receiver switched to mythtv, and it will send a wake on lan (WOL) packet to my mythtv to wake it up from S3 sleep, as well as restart X because for my video card X doesn't resume properly over HDMI.
At the same time, as explained in an earlier post on how to mute and change volume on a Denon receiver using a mythtv remote, I already have my mythtv send commands to my Denon AV receiver to change the volume or mute it.
Given the above, I now had the problem that both my hacks didn't work at the same time because denon receivers only accept one telnet connection. Thankfully the connection is 2-way, but you need the code that can receive status from it independently from writing to the telnet socket.
So, I found denon.pl from bradfitz and quickly adapted my code into it. My new code reads commands from a FIFO and passes them on ot the socket. With my diffs, it's not super pretty code, but I stopped caring when it worked :)
So now, I have my denon to trigger a WOL packet to wake up my mythtv PC from S3 ACPI sleep when it's selected in the receiver.
In turn, when my denon is told to sleep, my code receives the state change over the telnet connection and my controlling PC uses the info to make mythfrontend exist back to mythwelcome, which in turn will cause the mythtv to go to S3 sleep if it's not doing anything else.
This can all be done by only reading from the telnet connection (which I used to do), but now I also use mythtv to control the sound on the receiver and this is done by sending commands asynchronously from the status updates being received.
This is how the mute script works:
#!/bin/bash
FILE=/tmp/denonmute
test -f $FILE || touch $FILE
# prevent bounces
[ $(( $(date "+%s") - $(stat -c "%Z" $FILE) )) -lt 2 ] && exit
if grep -q MUON $FILE 2>/dev/null; then
CMD=MUOFF
else
CMD=MUON
fi
# Used to be:
#echo -ne "$CMD\r" | nc -q0 denon 23
#echo $CMD > $FILE
echo $CMD | tee $FILE > /var/run/denon.fifo
Here is the code that interacts with my Denon AVR:
denon
denoncmd
denonmute
On the mythtv side, these are some of my scripts:
myth_can_shutdown
#!/bin/bash
# See http://www.mythtv.org/wiki/ACPI_Wakeup
# Time wakeup format was hh:mm yyyy-MM-dd and changed to time_t
DATE=$(date "+%Y:%m:%d-%H:%M:%S")
LOG=/var/log/mythtv/myth_acpi
exec >>$LOG
exec 2>>$LOG
# This will shutdown if I pause too long without turning off denon.
#for mplayer in $(pgrep mplayer)
#do
# # See if mplayer is currently playing.
# before=$(grep pos /proc/$mplayer/fdinfo/*)
# sleep 2
# after=$(grep pos /proc/$mplayer/fdinfo/*)
#
# if [ "$before" != "$after" ]; then
# echo "$DATE: $0 found running mplayer, cancel sleep"
# ps auxww |grep "[ ]$mplayer"
# exit 1
# fi
#done
# mplayer is killed in myth_frontend_shutdown.
if pgrep mplayer; then
echo "$DATE: $0 found mplayer, cancel sleep"
ps auxww |grep "[mM]player"
exit 1
fi
if pgrep xbmc.bin >/dev/null; then
echo "$DATE: $0 xbmc running, cancel sleep"
exit 1
fi
if who | grep -qv mythtv; then
echo "$DATE: $0 root logged in, cancel sleep"
who
exit 1
fi
echo "$DATE: $0 called, and nothing blocking shutdown"
myth-fmr_wol
#!/bin/bash
etherwake -i eth1 00:22:15:8c:66:e9
etherwake -i wlan0 00:22:15:8c:66:e9 &>/dev/null
myth_frontend_shutdown is the script 'denon' calls via telnet to shut down mythfrontend/xmbc/mplayer and let the machine go to S3 sleep
#!/bin/bash
LOG=/var/log/mythtv/myth_acpi
exec >>$LOG
exec 2>>$LOG
if pgrep xbmc.bin >/dev/null; then
echo "$(date "+%Y:%m:%d-%H:%M"): Asking XBMC to shut down"
/var/local/scr/alarm 5 wget --quiet -O /dev/null 'http://localhost:8080/xbmcCmds/xbmcHttp?command=exit'
sleep 5
ps auxww | grep '[xX]bmc'
if pgrep xbmc.bin || pgrep xmbc; then
echo "$(date "+%Y:%m:%d-%H:%M"): XBMC DID NOT DIE, FORCIBLY KILLING."
killall xbmc.bin xbmc
sleep 1
killall -9 xbmc.bin xbmc
fi
fi
# stop mplayer so that myth_can_shutdown can detect and shutdown.
killall mplayer
# killing xbmc can restart mythfrontend.
sleep 1
if pgrep -f mythfrontend.real >/dev/null; then
echo "$(date "+%Y:%m:%d-%H:%M"): Asking mythfrontend pid $(pgrep -f mythfrontend.real) to shut down"
# http://www.mythtv.org/wiki/Keybindings
#for key in space f1 escape escape down enter
#do
#nc -q0 localhost 6546 <<< "key $key"
#sleep 1
#done
for cmd in 'jump mainmenu' 'key escape' 'key down' 'key enter'
do
echo "sending $cmd"
nc -q0 localhost 6546 <<< "$cmd"
sleep 1
done
# Key enter takes a long time to exit mythfrontend.
sleep 10
if pgrep -f mythfrontend.real; then
echo "$(date "+%Y:%m:%d-%H:%M"): MYTHFRONTEND DID NOT DIE, FORCIBLY KILLING."
/usr/bin/killall mythfrontend.real
sleep 1
/usr/bin/killall -9 mythfrontend.real
else
echo "$(date "+%Y:%m:%d-%H:%M"): mythfrontend has shut down."
fi
fi
myth_reset_xorg is the other script 'denon' calls via telnet to restart X and mythwelcome after S3 sleep restore. This is necessary on my system because X doesn't always come back ok from sleep.
#!/bin/bash
export PATH=/var/local/scr:$PATH
while :
do
# I shouldn't have to kill mythfrontend because it shouldn't be running
# but this is just for completeness.
# I have also had one case like below where I had to kill Xorg.
# |-login(6160)---startx(6184,mythtv)---xinit(6307)-+-Xorg(6308,root)
# | `-xinit(6318)
killall mythwelcome xinit mythfrontend.real mythfrontend Xorg
sleep 6
X_TTY=$(ps auxww | grep '/usr/bin/[X]' | sed -e "s/.* tty//" -e "s/[^0-9].*//")
/var/local/scr/alarm 3 sudo chvt 2
sleep 1
/var/local/scr/alarm 3 sudo chvt $X_TTY
sleep 2
# This doesn't actually check that X is talking to a screen, it works
# when waking up from RTC and no screen is connected :(
#if grep 'intel(0): EDID vendor "DON"' /var/log/Xorg.0.log; then
if grep 'Resuming AIGLX clients after VT switch' /var/log/Xorg.0.log; then
echo "X seems be running and displaying"
break
else
echo ">>>>>>>>>>>> X isn't displaying, trying restart loop <<<<<<<<<<<<<<<<"
fi
done
For indexing purposes, here is a snapshot of the main denon code
#!/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" |
|