#!/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
#
# Originally posted to KnoppMythWiki by Bret Shroyer <bret <at> bretshroyer ,dot, org> Dec 16, 2004
$|=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;
}