#!/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; }