#!/usr/bin/perl ############################################################################## ## bus.pl ############################################################################## ## Winnipeg Transit Bus shedule client for lcdproc, download other end at ## lcdproc.org. Starts off by downloading schedule files, then parses, ## then updates every $DELAY seconds re-downloads files daily at 3 am ## ## Make sure you customize in Customize section ## ## No cron stuff is needed for downloading, but garbage develops on my screen ## so i restart bus.pl and LCDd daily ## ## v.0.1 02/02/03 (c) 2003 Nathaniel Rosenstock rosensto@shaw.ca ############################################################################## use IO::Socket; use Fcntl; ############################################################################## # user-config section # ############################################################################## $HEARTBEAT="off"; ## on/off/slash (if LCDd config allows control from client) $HOST="localhost"; ## LCDd server address $PORT="13666"; ## LCDd server port $DEBUG=0; ## debug level- 0:no output 1:print some client/server comm. $LYNX="lynx"; ## path to lynx $SCHED_PATH="./"; ## local dir where downloaded schedules go $WEB_DIR='http://www.winnipegtransit.com/TIMETABLE/TODAY/STOPS/'; $UPDATE_AT=4; ## when the clock strikes this number, get new schedules ## schedules directory on site $MILITARY=0; ## 0=12 hour tmie 1=24 hour time ## configure how the display shows info, this is as simple as i could make it # this is mine: #$format=' \n OPT_SPACE'; # default $format="Next bus: "; ## Syntax: ## <[route|next],["stop number"|stop index],["route num"|route index],[dest|nodest]> ## type route displays the route number, you may think this is useless ## (as you could just type in the route number) ## but its point is that there are stops where some busses come ## some days, eg school busses, and you dont want the route ## number to be displayed and not the time ## You can indicate the first route by 0 or the specific with ## the actual rotue number eg "18" (in quotes) ## ## type next displays next bus time for that stop, route comb, in form HH:MM ## dest show destination symbol after time, eg "6:15D" for downtown ## nodest no destination character displayed my $DELAY="30"; ## check for new bus times every $DELAY seconds ############################################################################## # end of user-config section # ############################################################################## check_input(); ## parse input, show usage, etc download_stops(); ## only if -u or no schedules found parse_html(); ## parse shedules (done once per run) init_display(); ## initialize display so its ready for input $format=~s/\n//mg; $format=~s/\\n/\n/mg; while(1) {download_stops(); ## download shedule files if neccessary load_next(); update_display(); sleep($DELAY); ## how often to check new_day_check(); ## do we need to download new schedule } close ($remote) || die "close: $!"; ## $remote stream opened in init_display exit; ############################################################################### # update_display # ############################################################################### sub update_display { my $lcdresponse; my $row=0; if ($DEBUG) {print "\n";} foreach $line (split(/\n/m,$text)) ## convert newlines to new y coord on lcd {if($line) ## $text received as global {$row++; if ($DEBUG) {print localtime(time)." widget_set busgrep text$row 1". " $row {$line} ";} print $remote "widget_set busgrep text$row 1 $row {$line}\n"; sleep(1); ## wait for response $lcdresponse = <$remote>; ## will respond "success" if ($DEBUG) {print " $lcdresponse";} } } } ############################################################################### # init_display # ############################################################################### sub init_display { my $lcdresponse; $remote = IO::Socket::INET->new(Proto=>"tcp", PeerAddr=>$HOST, PeerPort=>$PORT,) || die "Cannot connect to LCDproc port\n"; ## Make sure our messages get there right away $remote->autoflush(1); sleep 1; ## Give server plenty of time to notice us... print $remote "hello\n"; $lcdresponse=<$remote>; if ($DEBUG) {print localtime(time)." $lcdresponse";} fcntl($remote, F_SETFL, O_NONBLOCK); print $remote "client_set name {bus_client}\n"; print $remote "screen_add busgrep\n"; print $remote "screen_set busgrep name {Bus} heartbeat $HEARTBEAT\n"; print $remote "widget_add busgrep text1 string\n"; print $remote "widget_add busgrep text2 string\n"; print $remote "widget_add busgrep text3 string\n"; print $remote "widget_add busgrep text4 string\n"; } ############################################################################### # parse_html ############################################################################### sub parse_html { my $stop_index=0; ## stop 1 2 or 3 as opposed to stop_num, 60401 60402 60380 my $route_index; ## 0,1,2, not 66,18.. my $hr; ## stop hour my $min; ## stop minute my $row; ## is new row my $col; ## is new column my $col_index; ## basically $route_index as each column is a route my $html; ## stores html of schedule # bus_stops[0] is routes, [1] is destinations foreach $stop_num (@stop_nums) {$route_index=-1; ## parse legend on left, add destinations to routes open (LEFT, $SCHED_PATH.$stop_num."left.html") ||die("Cannot load $stop_num"."left.html"); ## get rid of first line ; while() {$line=$_; ## parse title eg "78 Crosstown West" if($line=~/>(S?\d+) \w/) {$route_index++; $bus_stops[$stop_index][$route_index][0]=$1; } else {## parse destination eg "R To Riverbend" if($line=~/\>([\w#])\) {chop $_; $html.=$_; } close(SCHED); ## each row is new time foreach $row (split(/substr($list[$#list],0,4)) {$c=0;} else {## find next bus while(($time>=substr($list[$c],0,4))&&($c<=$#list)) {$c++;} } $next[$stop_index][$route_index][0]=$bus_stops[$stop_index][$route_index][0]; ## pull off hour $bus_hour=substr($list[$c],0,2); ## >= 24-hour to 12-hour time (see above) if($bus_hour>24) {substr($list[$c],0,2,($bus_hour-24));} else {if($MILITARY && $bus_hour==24) {substr($list[$c],0,2,($bus_hour-24));} if(!($MILITARY)) {if($bus_hour>12) {substr($list[$c],0,2,($bus_hour-12));} } } while(length($list[$c])<4) {$list[$c]="0".$list[$c];} ## clean up times $list[$c]=~s/\n//mg; $list[$c]=~s/(\d{1,2})(\d\d)/$1:$2/; $list[$c]=~s/^0{1,2}//; $list[$c]=~s/ 0/ /; $list[$c]=~s/ $//; $list[$c]=~s/^ //; $list[$c]=~s/^:/0:/; $next[$stop_index][$route_index][1]=$list[$c]; $route_index++; } ## end else $stop_index++; } ## end while ## separate the plain text, and the parseables, then put them back together $out=""; foreach $code ($format=~/(\<.*?\>)/g) {if($code=~/\<(.*?)\,(.*?)\,(.*?)(\,(.*?))?\>/) {my $function=$1; ## next or route my $stop=$2; ## stop num or index my $route=$3; ## route num or index my $ifdest=$5; ## dest or nodest my $stop_index=99; my $route_index=99; ## first get stop index out of either stop number or index if($stop=~/\"(\d+)\"/) {for ($g=0;$g<=$#stop_nums;$g++) {if($1 eq $stop_nums[$g]) {$stop_index=$g;} } } else {$stop_index=$stop;} if($stop=~/^(\d+)$/) {$stop_index=$1;} if($stop_index!=99) { if($route=~/"(\d+)"/) {for($route_count=0;$route_count<(@{$next[$stop_index]});$route_count++) {if($1 eq $next[$stop_index][$route_count][0]) {$route_index=$route_count;} } } else {$route_index=$route;} if($route_index!=99) { if($function eq "route") {$out.=$next[$stop_index][$route_index][0]."|";} else {$tmp=$next[$stop_index][$route_index][1]; if($ifdest eq "nodest") {chop($tmp);} $out.=$tmp."|"; } } else {$out.="|";} } } } ## dont want to change $format, unfortuneately this function is run every update $tmp=$format; foreach $code ($out=~/(.*?)\|/g) {$tmp=~s/\<(.*?)\>/$code/;} $text=$tmp; ### customized for me @lines=split(/\n/,$text); if ((length($lines[1])-8)>16) {$text=~s/OPT_SPACE//;} else {$text=~s/OPT_SPACE/ /;} ### } ############################################################################### # download_stops ############################################################################### sub download_stops { if($UPDATE==1) {foreach $stop_num (@stop_nums) {print localtime(time)." Attemping stop download $stop_num from \n".$WEB_DIR.$stop_num." .. "; $run1="$LYNX ".$WEB_DIR.$stop_num."bottom.html --source > ". $SCHED_PATH.$stop_num."bottom.html"; $run2="$LYNX ".$WEB_DIR.$stop_num."left.html --source > ". $SCHED_PATH.$stop_num."left.html"; `$run1`; `$run2`; if ((-s $SCHED_PATH.$stop_num."bottom.html")&&(-s $SCHED_PATH.$stop_num."bottom.html")) {print "Download successful\n\n"; $UPDATE=0; } else {print "Download failed\n\n";} } undef @bus_stops; undef @next; parse_html(); } } ############################################################################### # check_input ############################################################################### sub check_input { my $stop_count=0; $UPDATE=0; ## force update if 1, should do this once a day $usage="Usage: bus.pl [stopnunmber1] [stopnumber2] [-u]\n\n". " -u update shedule files, this is done daily automatically\n". " stopnumberX list of 5-digit transit stop numbers eg 60401\n\n\n". "Version 0.1 02/02/03 (c) 2003 Nathaniel Rosenstock \n\n"; foreach $arg (@ARGV) ## stop numbers from command line {if($arg=~/\d+/) {$stop_nums[$stop_count]=$arg; $stop_count++; } else {if($arg eq "-u") {$UPDATE=1;} else {print $usage; exit; } } } if($ARGV[0] eq "") {print $usage; exit; } foreach $stop_num (@stop_nums) {if( !(-e $SCHED_PATH.$stop_num."left.html") || !(-e $SCHED_PATH.$stop_num."bottom.html") ) {$UPDATE=1;} } } ############################################################################### # new_day_check ############################################################################### sub new_day_check { $hr=`date +%k`; chop($hr); if ( ($hr==$UPDATE_AT) && ($hr!=$hr_old) ) ## update when the clock strikes 3 am {$UPDATE=1;} else {$UPDATE=0;} $hr_old=$hr; }