#!/usr/bin/perl

# channel.pl - Version 1.1 (March 18, 2004)  
# Channel changing script for RCA DSS units via the serial port.
# By William Munson [wmunson@rochester.rr.com]
# Based on pieces pulled from various other DSS control scripts.
#
# This script accepts a 1 or more digit channel number on the command line
# or a single key command which must be defined in the keymap. The program also
# accepts the command 'macro' which is detailed later in the script.
# 
# 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
# these comments.
#
# Version 1.0 - First working version. Only supports channel change commands via 
#  channel number on the command line.
#
# Version 1.1 - Added support for commands other than a simple channel number.
#  Also added support for a macro command. For example this could be used
#  to send the keystrokes to buy a ppv if you associate the macro with a remote key.

$|=1;
use POSIX qw(:termios_h);
use FileHandle;

# Use verbose output mode for debugging.
$verbose=0;

# Time delay between each channel digit when a channel number is entered.
$inter_digit_delay=0.25;

# Enable to clear the on screen display more quickly.
# Warning: This slows down channel scrolling so use browse mode
# for best results or leave it disabled if you want snappier channel
# changes and can put up with the on screen display.
$quick_clear=1;

%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 => "0x9a",
  	  left => "0x9b",
	    up => "0x9c",
	  down => "0x9d",
      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",
	 clear => "0xf9",
	  menu => "0xf7");

# Variables used to process the channel number command line.
$position=0;
$digit="";
	  
# Start of main code.
$cmd_line_input=@ARGV[0];
	  
# Defaults to standard COM1 port, 9600 baud.
my $serial=init_serial("/dev/ttyS0","9600");

# Start of command decoding
if (length($cmd_line_input)) {
	# Check to see if a channel number was entered.
	if ($cmd_line_input != 0) {
		change_channel($cmd_line_input);
		exit; 
	}
	if ($cmd_line_input eq "macro") {
		# Do macro commands. Use your own key sequence here. 
		# Change it around as required for your needs.
		dss_key("down");
		sleep(1.0);
		dss_key("up");
		sleep(1.0);
		dss_key("select");
		sleep(1.0);
		dss_key("clear");
		exit;
	}
	# No special processing required. Send a single command.
	dss_key($cmd_line_input);
} else {
	print "You must enter a command for this script to work.\n";
}		

# begin subroutines.
sub change_channel {
	# Send a serial command for each digit of the channel number
	my ($channel_num)=@_;
	if ($verbose){
		print "Channel number: "; 
		print $channel_num;
		print "\n";
	}
	while ($position<length($channel_num)){
		$digit=substr($channel_num,$position,1);
		if ($verbose){
			print "Digit: ";
			print $digit;
			print "\n";
		}	
		dss_key($digit);
		$position++;
		sleep($inter_digit_delay);
	}
	#If enabled, clear the display after sleeping one second
	if ($quick_clear){
		sleep(1.0);
		print "Clear Display:\n" if ($verbose);
		dss_key("clear");
	}	
}	

sub dss_key {
    	my ($key)=@_;
    	return undef unless $keymap{$key};
    	simple_command("0x45","0x00","0x00",$keymap{$key});
}

sub simple_command {
    	if (defined(dss_command(@_))) {
		return(1);
    	} else {
		return(undef);
    	}
}

sub dss_command {
    	sendbytes("0xFA",@_);
    	return get_reply();
}


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;
}

