Marc's Public Blog - Linux Hacking


All | Aquariums | Arduino | Btrfs | Cars | Cats | Clubbing | Dining | Dinners | Diving | Electronics | Exercising | Flying | Hiking | Linux | Linuxha | Public | Rc | Sciencemuseums | Snow | Solar | Trips

This page has a few of my blog entries about linux, but my main linux page is here
Picture of Linus


>>> Back to post index <<<

2013/11/16 Mythtv + Denon-AVR-3808CI 2 Way Communication
π 2013-11-16 00:00 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"


    More pages: October 2016 August 2016 July 2016 June 2016 February 2016 January 2016 May 2015 March 2015 January 2015 October 2014 May 2014 April 2014 March 2014 January 2014 November 2013 September 2013 May 2013 March 2013 January 2013 December 2012 August 2012 May 2012 March 2012 January 2012 December 2011 August 2011 July 2011 January 2011 October 2010 August 2010 June 2010 April 2010 March 2010 January 2010 December 2009 November 2009 September 2009 August 2009 July 2009 May 2009 January 2009 December 2008 November 2008 October 2008 January 2008 November 2007 August 2007 July 2006 January 2006 August 2005 April 2005 November 2004 March 2004 February 2004

    >>> Back to post index <<<