#!/usr/bin/perl
# joypad Control a DSS receiver using a serial port connection
# version 0.1 - July 16, 2002
# Written by John Burwell
# Based remotely on dss_control by Josh Wilmes
#
# This software is under development and intended for private use only.
# It has not officially been licensed, but is provided at http://www.rgbdream.com/ as-is.
# Use of this software implies an agreement that the author is in no
# way liable for any consequences of its use.
# Modules to include:
use FileHandle;
use POSIX qw(:termios_h);
# Initial default settings:
$receiver_port = "/dev/ttyS1"; # The serial port to which the receiver is connected.
$receiver_baud = "9600"; # The baud rate for communicating with the receiver (usually 9600).
$verbose = 0; # The default verbosity level. 0 for almost no feedback, 1 for lots.
$response_timeout = 10; # Timeout, in seconds, for data to return from the receiver.
# Internal configuration:
$| = 1; # Flush the output buffer after every write to the serial port.
# Command function definitions:
%commands = (
"on" => [ \&power_on, "01", "\t\t\t- Turn on the receiver." ],
"off" => [ \&power_off, "02", "\t\t\t- Turn off the receiver." ],
"get_channel" => [ \&get_channel, "03", "\t\t- Show the current channel number." ],
"set_channel" => [ \&set_channel, "04", " \t- Change the channel to ." ],
"key" => [ \&key_command, "05", " \t\t- Simulate on the IR remote control." ],
"display" => [ \&text_display,"06", " \t- Display on-screen." ],
"scroll" => [ \&text_scroll, "07", " \t- Display as a scrolling marquee." ],
"show" => [ \&text_show, "08", "\t\t\t- Show on-screen text." ],
"hide" => [ \&text_hide, "09", "\t\t\t- Hide on-screen text." ],
"get_signal" => [ \&get_signal, "10", "\t\t- Show the current signal quality." ],
"boot_cold" => [ \&boot_cold, "11", "\t\t- Reset the receiver (clears EEPROM)." ],
"boot_warm" => [ \&boot_warm, "12", "\t\t- Reboot the receiver." ],
"ir_enable" => [ \&IR_enable, "13", "\t\t- Enable the IR remote control." ],
"ir_disable" => [ \&IR_disable, "14", "\t\t- Disable the IR remote control." ],
"verbose" => [ \&verbose, "15", "\t\t\t- Toggle verbose command output." ],
"help" => [ \&help, "16", "\t\t\t- Show quick help." ],
"exit" => [ \&quit, "17", "\t\t\t- Exit joypad." ]
);
# Remote control key hex code definitions:
%keys = (
"fetch" => [ "0x6C", "- Display the Fetch menu." ],
"right" => [ "0x9A", "- Right arrow key." ],
"left" => [ "0x9B", "- Left arrow key." ],
"up" => [ "0x9C", "- Up arrow key." ],
"down" => [ "0x9D", "- Down arrow key." ],
"favorite" => [ "0x9E", "- Display the Favorite menu." ],
"skip" => [ "0xAC", "- Choosy moms choose Jif." ],
"select" => [ "0xC3", "- Select/Info." ],
"exit" => [ "0xC5", "- Exit the current menu." ],
"9" => [ "0xC6", "- 9." ],
"8" => [ "0xC7", "- 8." ],
"7" => [ "0xC8", "- 7." ],
"6" => [ "0xC9", "- 6." ],
"5" => [ "0xCA", "- 5." ],
"4" => [ "0xCB", "- 4." ],
"3" => [ "0xCC", "- 3." ],
"2" => [ "0xCD", "- 2." ],
"1" => [ "0xCE", "- 1." ],
"0" => [ "0xCF", "- 0." ],
"ch_up" => [ "0xD2", "- Channel up." ],
"ch_dn" => [ "0xD3", "- Channel down." ],
"power" => [ "0xD5", "- Power On/Off." ],
"jump" => [ "0xD8", "- Jump/Previous channel." ],
"guide" => [ "0xE5", "- Display the guide." ],
"menu" => [ "0xF7", "- Display the menu." ],
"clear" => [ "0xF9", "- Clear the selection." ]
);
# Receiver response hex code definitions:
%responses = (
"0xF0" => [ "Valid command acknowledged.", 0 ],
"0xF1" => [ "Error: Invalid command.", -1 ],
"0xF2" => [ "Please wait...", 0 ],
"0xF3" => [ "Error: Timeout while waiting for command.", -1 ],
"0xF4" => [ "Command processing complete.", 1 ],
"0xF5" => [ "Error: Command processing failed.", -1 ],
"0xFB" => [ "Error: Unknown character input.", -1 ],
"0xFD" => [ "Error: Buffer overflow.", -1 ],
"0xFF" => [ "Error: Buffer overflow.", -1 ]
);
# Set up the serial port and create a handle:
my $serial = init_serial($receiver_port, $receiver_baud);
# Main command-line loop:
while( 1 ) {
print "joypad: ";
$_ = ;
next if ( $_ eq "\n" );
( $command, $argument ) = /(\S+)\s+(.*)/;
if( defined($commands{$command}) ) {
$return_value = &{ $commands{$command}[0] }( $argument );
print "\n${command}: ${return_value}\n\n" if( $verbose && $return_value );
}
elsif( defined($keys{$command}) ) {
$return_value = key_command( $command );
print "\n${command}: ${return_value}\n\n" if( $verbose && $return_value );
}
else {
print( "Command \"${command}\" not available.\nUse \"help\" to see a list of available commands.\n" );
}
}
# Channel command functions:
sub get_channel {
my @channel_hex = send_command( "0x07" );
if( scalar(@channel_hex) > 1 ) {
$channel = $channel_hex[0] * 256 + $channel_hex[1];
}
else {
$channel = $channel_hex[0];
}
print( "Current channel: $channel\n" );
return( $channel );
}
sub set_channel {
my( $channel ) = @_;
$_ = sprintf( "%4.4x", $channel );
( $n1, $n2 ) = /(..)(..)/;
send_command( "0x46", $n1, $n2, "0x0" );
}
# Remote control key functions:
sub IR_enable { send_command("0x13"); }
sub IR_disable { send_command("0x14"); }
sub key_command {
my( $key ) = @_;
if( !$keys{$key} ) {
print "Key not found.\nUse \"help\" to see a list of available keys.\n";
}
else {
send_command( "0x45", "0x00", "0x00", $keys{$key}[0] );
}
}
# On-screen display functions:
sub text_show { send_command("0x05"); }
sub text_hide { send_command("0x06"); }
sub text_display {
my( $text ) = @_;
my @hex_string;
if( !defined($text) ) {
send_command( "0x4A", "0x00" );
return;
}
foreach $character ( split( //, $text, 14 ) ) {
push( @hex_string, sprintf( "0x%x", ord($character) ) );
}
send_command( "0x4A", sprintf( "0x%x", scalar( @hex_string ) ), @hex_string );
}
sub text_scroll {
my( $text ) = @_;
$text = ( " " . $text );
text_display( $text );
while( $text ) {
text_display( $text );
$text =~ ( s/.// );
select( undef, undef, undef, 0.25 );
}
text_display();
}
# Receiver operation functions:
sub power_off { send_command("0x01"); }
sub power_on { send_command("0x02"); }
sub boot_cold { send_command("0x0A"); }
sub boot_warm { send_command("0x0B"); }
sub get_signal {
@signal_quality = send_command("0x10");
print( "Signal quality: @signal_quality\%\n" );
}
# Receiver interface functions:
sub send_command {
my( @bytes ) = ( "0xFA", @_ );
my $byte_hex = undef;
my $byte_char = undef;
print "\nSending data:\n" if( $verbose );
foreach $byte_hex ( @bytes ) {
$byte_hex =~ ( s/^0x//g );
$byte_hex = hex( $byte_hex );
$byte_char = pack( 'C', $byte_hex );
syswrite( $serial, $byte_char, length($byte_char) );
printf( "0x%X ", $byte_hex ) if( $verbose );
}
return( get_reply() );
}
sub get_reply {
$timestamp_begin = time();
$status_current = 0;
$status_previous = 0;
$byte_hex = 0;
$buffer = 0;
my( @bytes );
print "\n\nReceiving data:\n" if( $verbose );
while( 1 ) {
sysread( $serial, $buffer, 1 );
$byte_hex = sprintf( "0x%2.2X", ord($buffer) );
if( (time() - $timestamp_begin) > $response_timeout ) { die( "Error: timeout waiting for receiver response.\n" ) };
next if( $byte_hex eq "0x00" );
if( $responses{$byte_hex} ) {
print( "$byte_hex - $responses{$byte_hex}[0]\n" ) if( $verbose );
}
else {
$byte_hex =~ ( s/^0x//g );
$byte_hex = hex( $byte_hex );
print( " h> $byte_hex\n" ) if( $verbose );
push( @bytes, $byte_hex );
print( " a> @bytes\n" ) if( $verbose );
}
$status_current = scalar($responses{$byte_hex}[1]);
if( !defined($status_current) ) { $status_current = 0 };
last if( $status_current );
last if( $status_current == -1 && $status_previous == -1 );
$status_previous = $status_current;
}
if( $status_current > 0 ) {
return( @bytes );
}
else {
return undef;
}
}
# Program operation functions:
sub quit {
exit 0;
}
sub verbose {
$verbose = !$verbose;
if( $verbose == 1 ) {
print( "\nVerbose mode enabled.\n" );
}
}
sub help {
print( "\n\nJoypad controls DSS receivers via a serial interface.\nThese commands are available:\n\n" );
foreach $item (sort { @{$commands{$a}}[1] cmp @{$commands{$b}}[1] } keys %commands) {
printf( "%s%s\n", $item, $commands{$item}[2] );
}
print( "\nThe key command can simulate these key presses:\n\n");
foreach $item (sort { @{$keys{$a}}[0] cmp @{$keys{$b}}[0] } keys %keys) {
printf( "%s%s\n", $item, $keys{$item}[1] );
}
print( "\n" );
}
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";
$termios->getattr( $serial->fileno() ) || die "getattr: $!\n";
for( 0..NCCS ) {
last if( $_ == NCCS );
next if( $_ == VSTART || $_ == VSTOP );
$termios->setcc( $_, 0 );
}
$termios->setattr( $serial->fileno(), TCSANOW ) || die "setattr: $!\n";
return $serial;
}