#!/usr/bin/perl -w # # manage a window of screen info # on a LCD display # # # Original Example/Demo Code # -------------------------- # http://www.Linux-1U.net/LCD/get_kb_data.lcd.pl # # Keyboard ScanCodes # ------------------ # http://www.win.tue.nl/~aeb/linux/kbd/ Linux Keyboard and Console HowTO # http://ibiblio.org/gferg/ldp/Console-Programming-HOWTO/a321.html # http://www.geocities.com/SiliconValley/Park/7113/GameLib/lowLevelKeyboardDriver.html # http://www.geocities.com/SiliconValley/Park/7113/GameLib/writeKeyboardDriver.html # # http://www.execpc.com/~geezer/osd/kbd/ ( BIOS level ) # http://www.nondot.org/sabre/os/files/HCI/KeyboardFAQ.txt ( bios level ) # http://www-users.cs.umn.edu/~arashid/cached/IntroductiontoKeyboardHandlers.htm # # # Keyboard handler # --------------- # http://www.linuxdoc.org/LDP/lkmpg/node25.html # # # Additional Docs for Reading a key from the kb # --------------------------------------------- # http://theoryx5.uwinnipeg.ca/CPAN/perl/pod/perlfaq5/How_can_I_read_a_single_character_from_a_file_From_the_keyboard_.html # http://www.rocketaware.com/perl/perlfaq5/How_can_I_read_a_single_characte.htm # # # Read Characters from the regular ps/2 keyboard and display on screen ... ( LCD ) # - write to the LCD port instead of the stdout screen # # # 01-Dec-01 amo Date-of-Birth -- not too bad for 5 minutes work ( proof of concept ) # 17-Sept-02 srs Reinvtented as a LCD controller # # Copyright C2002 # License is the same as the Apache Web Server licence, look it up. # (for now anyway) # ############################## General Setup #################### use strict; require Term::ReadKey; use Term::ReadKey; # GLOBALS to CONFIGURE # which serial port is the display attached to #my ( $SERIALPORT ) = "/dev/ttyS0"; my ( $SERIALPORT ) = "/dev/ttyS1"; my($Banner) = "Linux 1RU"; # line #1 - undef or set to "" to not have it my($RefreshTimeout) = 10; # time to wait to refresh (seconds,-1 for no-refresh) my($clearOnExit) = 0; #clear the LCD when this program exits? my($debug) = 1; # 2 is more verbose 0 is none, 1 is standard # GLOBALS my($lcd_cmd,$lcd_cls,$lcd_gotoline1,$lcd_gotoline2,$lcd_numlines,$lcd_numcols); my($lcd_type) = "ituner"; my($lcd_input); # if this is defined, set to a serial port for entry # and "kbd" events will be processed from it $| = 1; # force a flush after our every print # window globals my(@window) = (); my($winpos,$winlen); ############################## The Code.... #################### # setup the input and LCD sub setup { # output LCD if ($lcd_type =~ /bpi-216/i) { system ( "stty 9600 <$SERIALPORT" ); open ( LCDOUT, "> $SERIALPORT") or die "could not open $SERIALPORT\n"; $lcd_cmd = "\xFE"; # general command prefix $lcd_cls = $lcd_cmd."\x01" ; # clear display $lcd_gotoline1 = $lcd_cmd."\x02".$lcd_cmd."\xEF" ; $lcd_gotoline2 = $lcd_cmd."\xC0"; $lcd_numlines = 2; $lcd_numcols = 16; } elsif ($lcd_type =~ /ituner/i || $lcd_type =~ /lk202-25/) { system ( "stty 19200 <$SERIALPORT" ); open ( LCDOUT, "> $SERIALPORT") or die "could not open $SERIALPORT\n"; $lcd_cmd = ""; # general command prefix (not used) $lcd_cls = "\x0c" ; # clear display $lcd_gotoline1 = "\xfe\x48" ; $lcd_gotoline2 = $lcd_gotoline1."\x0a"; $lcd_numlines = 2; $lcd_numcols = 20; # use IPC::Open2; # open2(\*LCDIN, LCDOUT, "cat $SERIALPORT 2>&1"); } else { print STDERR "Unknown lcd type!!! ($lcd_type). Bailing.\n"; exit 1; } # input LCD (if present) if ($lcd_type =~ /bpi-216/i) { # no input # open (LCDIN, "< /tmp/testinput"); # not really useful } elsif ($lcd_type =~ /ituner/i || $lcd_type =~ /lk202-25/) { if (not defined $lcd_input) {$lcd_input = $SERIALPORT;} system ( "stty 19200 <$SERIALPORT" ); open ( LCDIN, "< $SERIALPORT") or die "could not open $SERIALPORT\n"; } # turn on flushing select((select(LCDOUT), $|=1)[0]); print LCDOUT "$lcd_cls"; # setup window abstraction wm_setup(); } # close up shop sub teardown { if ($clearOnExit) {printf LCDOUT "$lcd_cls";} close(LCDOUT); close(LCDIN) if ($lcd_input); ReadMode 0; # Reset tty mode before exiting } # get input chars (for now - random info from sub fill_window { # title wm_append($Banner) if (defined $Banner and ((length $Banner) > 0)); # Network info my ( $host ) = `hostname`; chomp ( $host ); wm_append("net $host"); my ( @ipaddr ) = split ( /:/, `ifconfig -v | grep -i "inet addr" | grep -v 127.0.0 ` ); if ($#ipaddr>0) { my ( $ip ) = $ipaddr[1]; chomp ( $ip ); $ip =~ s/\s.*//; # nuke end wm_append("$ip"); my($mask) = $ipaddr[3]; chomp ( $mask ); $mask =~ s/\s.*//; # nuke end wm_append(" $mask"); } # 2nd interface (untested) if ($#ipaddr >= 6) { my ( $ip ) = $ipaddr[4]; chomp ( $ip ); $ip =~ s/\s.*//; # nuke end wm_append("$ip"); my($mask) = $ipaddr[6]; chomp ( $mask ); $mask =~ s/\s.*//; # nuke end wm_append(" $mask"); } my(@route) = split (/\s+/,`route -n|grep '^0.0.0.0'`); if ($#route > 0) { chomp($route[1]); wm_append(">" . $route[1]); } else { wm_append(">No network is stetup(!)"); } # load info my($upinfo) =`uptime`; $upinfo =~ s/\s\s/ /g; # nuke extra space $upinfo =~ s/^\s+//; # nuke inital space $upinfo =~ m/(.*),/; # grab time/uptime wm_append($1); $upinfo =~ m/\s*(\d*) users/; wm_append("Users " . $1 . " " . `users`); $upinfo =~ s/\s*load average:/Load/; $upinfo =~ m/(Load.*)/; wm_append ($1); wm_append("PS:"); my($line); foreach $line (`ps -eo '%cpu %mem args' --sort='-%cpu,-%mem'`) { chomp($line); $line =~ s/^\s+//; # kill initial space(s) $line =~ s/\s\s+/ /g; # kill extra space(s) wm_append($line); } } sub init_data { fill_window(); } sub refresh_data { wm_clear(); fill_window(); } # send output to LCD, 2 lines, presenting the 2 lines in the window at winpos sub output { if ($debug>1) { print "OUTPUT, position $winpos, window is:\n"; my($line); foreach $line (@window) { print "$line\n"; } } my($line1) = wm_get($winpos); my($line2) = wm_get($winpos+1); print "L1: $line1!\n" if ($debug and defined $line1); print "L2: $line2!\n" if ($debug and defined $line2); my($oldfh) = select LCDOUT; if (defined $line1 && ($line1 ne "")) { print "$lcd_gotoline1"; print substr $line1, 0, $lcd_numcols; my($len) = length $line1; my ($diff) = $lcd_numcols - $len; if ($diff>0) {print " " x $diff;} } if (defined $line2 && ($line2 ne "")) { print "$lcd_gotoline2"; print substr $line2, 0, $lcd_numcols; my($len) = length $line2; my ($diff) = $lcd_numcols - $len; if ($diff>0) {print " " x $diff;} } select $oldfh; } # read one "character" (i.e. keypress including multi-char esc codes) # and call a processing routine on it, then return it. # This should use a keymap of functions like ReadLine. # in reality it is brute force. # return vals: # 0 - exit # 1 - normal sub process_kbd { my($key); ReadMode 3; # while (1) { # $key = ReadKey(($RefreshTimeout>0) ? $RefreshTimeout : 0); # if ((not defined $key) and ($RefreshTimeout>0)) { # # timed out, so do a refresh # print "Refreshing\n" if ($debug>1); # &refresh_data; # } else { # last; # } # } $key = ReadKey(-1); return 1 if (not defined $key); print "Got key $key\n" if ($debug); return process_char($key); } sub process_char { my($key) = @_; my($retval)=1; # Escape chars have multiple characters to follow if ($key =~ /[0-9]/) { &wm_goto($key);} elsif ($key =~ /q/i) { $retval = 0;} elsif ($key =~ /r/i) { refresh_data();} elsif ($key =~ / /) { refresh_data();} elsif ($key =~ /a/i) { alvin_example();} elsif ($key =~ /n/i) { &wm_search("net");} # goto net elsif ($key =~ /p/i) { &wm_search("ps");} # goto PS elsif ($key =~ /t/i) { type_mode();} # fancy elsif ($key =~ /[A-Za-z>!@]/) { &wm_search($key);} # if not special, search elsif ($key eq "") {$retval = 0;} elsif ($key eq "") { print " multipart...." if ($debug); # this is a hack.... # Quickly read in the rest, unbuffered, since we assume all is there # could maybe fail over slow connections or something. my($keystring) = ""; my($newkey)=""; do { $keystring .= $newkey; $newkey = ReadKey(-1); # nonblocking read } while (defined $newkey); print "String is $keystring!\n" if ($debug>1); if ($keystring eq "[A") {&wm_up; # up arrow } elsif ($keystring eq "[B") {&wm_down; # down arrow } elsif ($keystring eq "[C") {&wm_up; # right arrow } elsif ($keystring eq "[D") {&wm_down; # left arrow } elsif ($keystring eq "[1~") {&wm_top; # HOME } elsif ($keystring eq "[4~") {&wm_bot; # END } elsif ($keystring eq "[[A") {&wm_down; # F1 } elsif ($keystring eq "[6~") {&do_screendown; # PageDown } elsif ($keystring eq "[5~") {&do_screenup; # PageUp } } return $retval; } sub process_lcd_char { my($key) = @_; my($retval)=1; if ($lcd_type =~ /ituner/i) { if ($key eq "O") { refresh_data();} elsif ($key eq "D") {&wm_up;} elsif ($key eq "J") {&wm_down;} elsif ($key eq "E") {$retval = 0;} else {print "No function for that key...($key)\n";} } else { # full lk202-25/) print "No function for that key...($key)\n"; } return $retval; } sub process_lcd { my($key,$retval); print "reading...\n"; ReadMode(3, \*LCDIN); $key = ReadKey(-1, \*LCDIN); if (defined $key) { print "Got LCD $key\n" if ($debug); $retval = process_lcd_char($key); } else { $retval = 1; } return $retval; } ############################## Sample Functions #################### # Functions can be called in two ways - bind them to a keystroke # in which case you add a hook to process_kbd # or selected out of an LCD controller - in which case process_lcd # gets the hook. sub alvin_example { # append 2 lines at the bottom of the window wm_append("Provided by..."); wm_append("Alvin & Steve"); wm_bot(); # go to the bottom of the window } sub do_screendown { for(my($i)=0; $i<$lcd_numlines; $i++) { wm_down(); } } sub do_screenup { for(my($i)=0; $i<$lcd_numlines; $i++) { wm_up(); } } sub type_mode { my($key)=""; my($buf)=""; print "Type a one line message which will appear at end of window\n"; do { if ($key ne "" and $key ne "") { $buf .= $key; } else { my($len) = length $buf; if ($len>0) { $buf = substr($buf, 0, $len-1); } } print $key; $key=ReadKey(); } while (defined $key and $key ne "\n"); &wm_bot; # go close to the bottom my($line); foreach $line (split /\n/, $buf) { print "append $line\n" if ($debug>1); wm_append($line); } # go to top of new input/close to it anyway do_screendown(); print "Back to main read loop...\n"; } # Windowing subs sub wm_setup {$winpos=0; $winlen = $lcd_numlines;} sub wm_clear { @window = ();} sub wm_top {$winpos = 0;} sub wm_bot {$winpos = $#window; &wm_bounds;} sub wm_goto {$winpos = @_; &wm_bounds;} sub wm_size {return $#window;} sub wm_up {$winpos--; &wm_bounds;} sub wm_down {$winpos++; &wm_bounds;} sub wm_bounds { if ($winpos < 0) {$winpos = 0;} if ($winpos > $#window-($winlen-1)) {$winpos = $#window-($winlen-1);} } sub wm_append {push @window, @_;} sub wm_getpos {return $winpos;} # find a line starrting with the arg string (ignore case) sub wm_search { my($find) = @_; for(my($i)=0; $i<$#window; $i++) { if ($window[$i] =~ m/^$find/i) { $winpos = $i; &wm_bounds; return; } } &wm_bot; } sub wm_get { my($line) = @_; if (defined $line) {return $window[$line];} return $window[$winpos]; } # Main sub main { # process args while ($#ARGV>=0) { if ($ARGV[0] =~ m/-type/i) { # get type shift @ARGV; $lcd_type = $ARGV[0]; print "Set type to $lcd_type\n" if ($debug); } elsif ($ARGV[0] =~ m/-in/i) { shift @ARGV; $lcd_input = $ARGV[0]; print "Set input to $lcd_input\n" if ($debug); } else { print qq@I don't understand "@ . $ARGV[0] . qq@"\n@; print "usage: [-type {bpi-216|ituner|lk202-25}] [-in ]\n"; exit; } shift @ARGV; } setup; print "Welcome to the LCD controller...\n"; # setup the window buffer init_data; # process keystrokes and redisplay the window print qq@Type commands to navigate: ARROWS - move forward and back HOME - goto top; END - goto end PageDown/Up - move by a page n - goto network info p - goto PS info # - goto line # a - alvin demo t - typing mode ^L or r - refresh info q or ^D - quit OTHER KEY - search for a line starting with that key @; my($result)=1; do { output; $result = process_kbd; if (defined $lcd_input) { $result = $result && &process_lcd(); ; } sleep(1); # gross but dont be so busy } while ($result>0); print "\n"; teardown; } # and run.... main; exit;