#!/usr/bin/perl # rca_control.pl: Remote control of a RCA DirecTV unit via the serial port # By Josh Wilmes (http://www.hitchhiker.org/dss) # Based on info from http://www.isd.net/mevenmo/audiovideo.html # # I take no responsibility for any damage this script might cause. # Feel free to modify and redistribute this as you see fit, but please retain # the comments above. # Modified by Dave Manaloto < dave@practicecode.com > # - Put discrete code to turn on IRD # - Try and clear OSD after changing channel # - sync computer to IRD time "--sync-time" # Modified by John Gruenenfelder < johng@as.arizona.edu > # - Use codes for newer RCA model receivers (same as Sony codes?) # Info from http://www.dar.net/~andy/tivo/rca_dss_serial.html # - Use Perl's select function to pause 0.20 seconds rather than external # usleep call # - Use 9600 baud rate instead of 115200 $|=1; use POSIX qw(:termios_h); use FileHandle; # # Verbose output # $verbose=1; # # Serial port settings. Change to suit. Baudrate probably doesn't need to be # changed. # $baudrate = "9600"; $serport = "/dev/ttyS0"; # # Delay between sending channel change command and clearing the on-screen # display. 0.20 seconds seems to be fine. Must be > 0 otherwise OSD won't # actually get cleared. # $clearosd_delay = 0.20; %pkt_decode=("0xF0" => "START PKT", "0xF1" => "ERR 1", "0xF2" => "GOT EXTENDED", "0xF4" => "END PKT", "0xF5" => "ERR 2", "0xFB" => "PROMPT"); %terminal=("0xF1" => -1, "0xF4" => 1, "0xF5" => -1); %keymap=(right => "0xa8", left => "0xa9", up => "0xa6", down => "0xa7", favorite => "0x9e", select => "0xc3", exit => "0xc5", 9 => "0xc6", 8 => "0xc7", 7 => "0xc8", 6 => "0xc9", 5 => "0xca", 4 => "0xcb", 3 => "0xcc", 2 => "0xcd", 1 => "0xce", 0 => "0xcf", ch_up => "0xd2", ch_dn => "0xd3", power => "0xd5", jump => "0xd8", guide => "0xe5", menu => "0xf7"); my $serial=init_serial($serport,$baudrate); exit if($#ARGV == 1); # Make sure reciever is on turn_on_ird(); if($ARGV[0] == "--sync-time") { dss_sync_time(); exit(0); } $ret=&change_channel(@ARGV); select(undef, undef, undef, $clearosd_delay); clear_osd(); exit(0); sub simple_command { if (defined(dss_command(@_))) { return(1); } else { return(undef); } } sub dss_command { sendbytes("0xFA",@_); return get_reply(); } sub change_channel { my ($channel)=@_; $_=sprintf("%4.4x",$channel); ($n1,$n2)=/(..)(..)/; simple_command("0xA6",$n1,$n2,"0xff","0xff"); } sub turn_on_ird { printf("Turning on IRD\n") if ($verbose); simple_command("0x82"); } sub clear_osd { printf("Clearing OSD\n") if ($verbose); simple_command("0xA5","0x00","0x00","0xf9"); } sub dss_sync_time { printf("Synching time to IRD\n") if ($verbose); dss_set_time(dss_get_time()); } sub dss_get_time { printf("Getting time\n") if ($verbose); @ret = dss_command("0x91"); return @ret; } sub dss_set_time { @inTime = @_; return if($#inTime != 6); $strTime = "$inTime[1]/$inTime[2] $inTime[3]:$inTime[4]:$inTime[5]"; print("Setting system time to $strTime\n") if ($verbose); $cmd = "date -s \"$strTime\""; `$cmd`; } sub sendbytes { (@send)=@_; foreach (@send) { s/^0x//g; $_=hex($_); } print "SEND: " if ($verbose); foreach $num (@send) { $str=pack('C',$num); printf("0x%X [%s] ", $num, $str) if ($verbose); syswrite($serial,$str,length($str)); } print "\n" if ($verbose); } sub get_reply { my $starttime=time(); my ($last,$ok,@ret); print "RECV: " if ($verbose); while (1) { $ret=sysread($serial,$buf,1); $str=sprintf("0x%2.2X", ord($buf)); # busy wait bad! die ("Error ($str)\n") if (time() - $starttime > 8); next if $str eq "0x00"; if ($pkt_decode{$str}) { print $str if ($verbose); print "[$pkt_decode{$str}] " if ($verbose); } else { $_=$str; s/^0x//g; $_=hex($_); printf("$str(%3.3s) ",$_) if ($verbose); push (@ret,$_); } $ok=1 if ($terminal{$str} > 0); last if ($terminal{$str}); last if ($last eq "0xFB" && $str eq "0xFB"); $last=$str; } print "\n\n" if ($verbose); return @ret if ($ok); return undef; } sub init_serial { my($port,$baud)=@_; my($termios,$cflag,$lflag,$iflag,$oflag); my($voice); my $serial=new FileHandle("+>$port") || die "Could not open $port: $!\n"; $termios = POSIX::Termios->new(); $termios->getattr($serial->fileno()) || die "getattr: $!\n"; $cflag= 0 | CS8 | HUPCL | CREAD | CLOCAL; $lflag= 0; $iflag= 0 | IGNBRK | IGNPAR | IXON | IXOFF; $oflag= 0; $termios->setcflag($cflag); $termios->setlflag($lflag); $termios->setiflag($iflag); $termios->setoflag($oflag); $termios->setattr($serial->fileno(),TCSANOW) || die "setattr: $!\n"; eval qq[ \$termios->setospeed(POSIX::B$baud) || die "setospeed: \$!\n"; \$termios->setispeed(POSIX::B$baud) || die "setispeed: \$!\n"; ]; die $@ if $@; $termios->setattr($serial->fileno(),TCSANOW) || die "setattr: $!\n"; # This gets rid of all the special characters.. $termios->getattr($serial->fileno()) || die "getattr: $!\n"; for (0..NCCS) { if ($_ == NCCS) { last; } # Dont mess up XON/XOFF.. if ($_ == VSTART || $_ == VSTOP) { next; } $termios->setcc($_,0); } $termios->setattr($serial->fileno(),TCSANOW) || die "setattr: $!\n"; return $serial; }