#!/usr/bin/perl ########################################################################## # # This script is to be used with a Toyota Prius 2004,2005 in conjunction # with a CAN-view http://hybridinterfaces.ca/ # # Will read in MFD touchscreen coordinates sent by the CAN-view and # translate them into Apple Front Row AppleScript commands (key presses) # to control FrontRow # email: Jeremy Kusnetz jeremy@kusnetz.net for details or see # http://www.kusnetz.net/prius/ # # This script requires Mac::AppleScript::Glue,Device::SerialPort,Time::HiRes # Use CPAN to download and install these modules. # # This script will need tweaking to work on your own system. # If your Mac spontaneously combusts after running this scrip I am not responsible! # Be sure the serial port is pointing to your serial port. # # This script will now listen to a port and spit out XML containing the CAN-view # formated messages! my $version = '2006.09.27,01'; my $debug = 2; # Debug 0=No output, 1=Mild debug output, 2=Verbose debug output. my $fake = 0; # If fake then don't do serial, but make up numbers my $genfake = 0; # Generate fake data use strict; use Socket; use IO::Socket; use POSIX qw(:sys_wait_h); use Symbol; use Fcntl qw(:DEFAULT); use IPC::Shareable; use Device::SerialPort; use POSIX qw(strftime); use Time::HiRes qw(usleep gettimeofday sleep); use strict; use Mac::AppleScript::Glue; use Mac::AppleScript qw(RunAppleScript); use DBD::mysql; use DBI; $/ = "\0"; my $PORT = 7890; my $buffer = ""; my $touchscreenScrollDirection = ""; my %CAN = (); my $elapsedTotal = 0; my $lastsec = 0; my $dblastsec = 0; my $milesTotal = 0; my $gallonsTotal = 0; my $lastMileElapsedTotal = 0; my $lastMileTotal = 0; my $lastMileGallonsTotal = 0; my $lastMph = -1; my $lastSOC = -1; my $mphAvg; my $mpgAvg; my $lastMileMpgAvg; my $lastMileMphAvg; my $lastStage; my %db = (); # Set to your serial port number #my $port = "/dev/ttys0"; my $port = "/dev/tty.KeySerial1"; my $baud = "115200"; my $inputData = 255; my $ob; my @rv = (); my $handle = tie $buffer, 'IPC::Shareable', undef, ; my $touchscreenHandle = tie $touchscreenScrollDirection, 'IPC::Shareable', undef, ; #my $listen = new Net::Shared::Handler; #my $new_shared = new Net::Shared::Local ( name=>"new_shared", port=>3252); #$listen->add(\$new_shared); $SIG{INT} = $SIG{TERM} = \&sig_handler; $SIG{PIPE} = 'IGNORE'; #$SIG{CHLD} = \&REAPER; $SIG{CHLD} = 'IGNORE'; print "canview.pl, version: $version\n"; daemonize() if (!$debug); my $dbh = DBI->connect("DBI:mysql:database=canview;host=localhost;mysql_connect_timeout=10", "canview", "CANVIEW", {'RaiseError'=>0}); my $server = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $PORT, Listen => SOMAXCONN, Reuse => 1) or die "Couldn't be a tcp server on port $PORT : $@\n"; my $systemevents = new Mac::AppleScript::Glue::Application('System Events'); my $frontrow = new Mac::AppleScript::Glue::Application('Front Row'); touchscreenScroll(); # Fork off touchscreen scroll getCANData(); # Fork off CAN-view data processing # Listen on Socket for PSDAnim Flash my $client = undef; while ($client = $server->accept()) { my $pid = undef; next if $pid = fork; die "fork: $!" unless defined $pid; close $server; print "= Socket Child spawned ($$)\n" if ($debug > 1); $client->autoflush(1); print "- Incoming socket!\n" if ($debug); while (my $req = <$client>) { print "REQUEST: $req\n" if ($debug > 1); &process_request($client); } print "= Child terminated.\n" if ($debug > 1); close $client; exit; } close $client; # END MAIN # PSDAnim Flash Socket Functions sub process_request { my $client = shift; &transmit($client); } sub transmit { my $client = shift; # Decode, and transmit all at once. We do this so we # can tell when to send the "done transmitting" message. #$buffer = $listen->retrieve($new_shared); print $client "$buffer\0"; print "= Socket closed.\n" if ($debug); } # CAN-view data functions sub getCANData { srand; my $pid = undef; return if ($pid = fork); die "fork: $!" unless defined $pid; print "= CAN-view Child spawned ($$)\n" if ($debug > 1); $SIG{INT} = $SIG{TERM} = \&sig_handler; $SIG{PIPE} = 'IGNORE'; $SIG{CHLD} = \&REAPER; if (!$fake) { print "Setting up $port\n" if ($debug); $ob = Device::SerialPort->new ($port,1) || die "Can't open $port: $!"; $ob->baudrate($baud) || die "fail setting baudrate"; $ob->parity("even") || die "fail setting parity"; $ob->stty_inpck; $ob->stty_istrip; $ob->databits(8) || die "fail setting databits"; $ob->stopbits(1) || die "fail setting stopbits"; $ob->handshake("none") || die "fail setting handshake"; # timeouts #$ob->read_char_time(5); # time between chars $ob->read_const_time(90); # timeout after n $ob->write_settings || die "no settings"; print "Finished setting up $port\n" if ($debug); } my %CAN = (); open(XML,">canview.xml") if ($fake); while (1) { %CAN = readCANview(\%CAN); $handle->shlock(); $buffer = "store($new_shared, $buffer); $handle->shunlock(); } close(XML) if ($fake); exit; } sub readCANview { my ($temphash) = @_; my %CANOLD = %$temphash; my ($s, $usec) = gettimeofday(); my $cursec = ($usec / 1000000) + $s; $lastsec = $cursec if (!$lastsec); my $elapsed = ($cursec - $lastsec); $elapsedTotal += $elapsed; $lastMileElapsedTotal += $elapsed; $lastsec = $cursec; my ($grabbed, $datagrabbed) = (); if (!$fake) { ($grabbed, $datagrabbed) = $ob->read($inputData); } else { if ($genfake) { $grabbed = 30; for (my $i = 1; $i<$grabbed; $i++) { my $byte = int(rand(255)); $datagrabbed .= pack("C",$byte); } sleep(0.1666); } else { @rv = getRecordedData() if (scalar(@rv) == 0); my $hash = shift(@rv); $grabbed = $$hash{grabbed}; $datagrabbed = $$hash{datagrabbed}; my $sec = $$hash{epoch_sec}; my $usec = $$hash{epoch_usec}; my $dbsec = ($usec / 1000000) + $sec; $dblastsec = $dbsec if (!$dblastsec); my $dbelapsed = ($dbsec - $dblastsec); #print "$dbsec - $dblastsec = $dbelapsed\n"; $dbelapsed = 1.666 if ($dbelapsed > 5); sleep($dbelapsed) if ($dbelapsed > 0); $dblastsec = $dbsec; } } insertDatabase($s,$usec,$grabbed,$datagrabbed) if ((!$fake) || ($genfake)); my $coord = -1; my %POS = (); $POS{VOLTS} = 0; $POS{CURRENT} = 1; $POS{BATKW} = 2; $POS{JOINTKW} = 3; $POS{SOC} = 4; $POS{ICERPM} = 8; $POS{FUELFLOW} = 10; $POS{MG2RPM} = 11; $POS{ICETEMP} = 13; $POS{THROTTLE} = 14; $POS{MG1RPM} = 16; $POS{BRAKE} = 17; $POS{MPG} = 19; $POS{SPECIAL} = 27; if ($grabbed) { my @data = unpack('C*', $datagrabbed); #print "$grabbed: " . join(",", @data) . "\n"; if ($grabbed >= 30) { my $offset = 0; if ($grabbed == 31) { $offset = 1; } elsif ($grabbed >= 32) { if (sprintf("%02X", $data[30]) eq "EE") { $coord = $data[31]; } if (sprintf("%02X", $data[31]) eq "EE") { $coord = $data[32]; $offset = 1; } } foreach (keys %POS) { my $tempoffset = 0; $tempoffset = -1 if (($offset) && (($_ eq "VOLTS") || ($_ eq "CURRENT"))); $CAN{$_} = $data[$POS{$_} + $offset + $tempoffset]; } } elsif (($grabbed == 1) || ($grabbed == 2)) { $coord = $data[0]; } if ($coord >= 0) { printf("TOUCHSCREEN: %02X\n", $coord) if ($debug > 2); touchscreen($coord); } my $gear = "DRIVE"; my $polarity = 1; my $special = $CAN{SPECIAL}; if ($CAN{SPECIAL} >= 128) { $special = $special - 128; $polarity = 1; } else { $polarity = -1; } $gear = "PARK" if ($special == 0); $gear = "REVERSE" if ($special == 1); $gear = "NEUTRAL" if ($special == 2); $CAN{GEAR} = $gear; $CAN{ICETEMP} = sprintf("%.2f", $CAN{ICETEMP} * 1.8 + 32); $CAN{VOLTS} = $CAN{VOLTS} + 100; $CAN{MG1RPM} = $CAN{MG1RPM} * 100; $CAN{MG2RPM} = $CAN{MG2RPM} * 25; $CAN{MG2RPM} = $CAN{MG2RPM} * -1 if ($gear eq "REVERSE"); $CAN{ICERPM} = $CAN{ICERPM} * 25; $CAN{FUELFLOW} = $CAN{FUELFLOW} / 10; $CAN{BATKW} = $CAN{BATKW} / 10; $CAN{MG1RPM} = $CAN{MG1RPM} - 25000 if ($CAN{MG1RPM} > 12700); my $mph = $CAN{MG2RPM} / 55.177; #my $mph = $CAN{MG2RPM} / 52.986; $CAN{MPH} = sprintf("%0.1f", $mph); $CAN{CURRENT} = $CAN{CURRENT} * $polarity; my $batkw = $CAN{BATKW} + .5; $CAN{BATKW} = $CAN{BATKW} * $polarity * -1; $batkw = $batkw * $polarity * -1; my $milesElapsed = ($mph / 3600) * $elapsed; $milesTotal += $milesElapsed; $lastMileTotal += $milesElapsed; $lastSOC = -1 if (!$lastSOC); $lastMph = $CAN{MPH} if (($lastMph == -1) && ($CAN{MPH})); $lastSOC = $CAN{SOC} if (($lastSOC == -1) && ($CAN{SOC}) > 0); if ($lastMileTotal > 1) { printf("%.1f: %.3fMPH %.3fMPG\n",$lastMileTotal,$lastMileMphAvg,$lastMileMpgAvg); $lastMileTotal = $milesElapsed; $lastMileElapsedTotal = $elapsed; $lastMileGallonsTotal = 0; # Do Database Insert Here (Future) } my $gallonsElapsed = 0; $gallonsElapsed = $milesElapsed / $CAN{MPG} if (($CAN{MPG} > 0) && ($CAN{MPG} < 255)); $gallonsTotal += $gallonsElapsed; $lastMileGallonsTotal += $gallonsElapsed; $mpgAvg = $milesTotal / $gallonsTotal if ($gallonsTotal > 0); $mphAvg = $milesTotal / ($elapsedTotal / 3600) if ($elapsedTotal > 0); $lastMileMpgAvg = $lastMileTotal / $lastMileGallonsTotal if ($lastMileGallonsTotal > 0); $lastMileMphAvg = $lastMileTotal / ($lastMileElapsedTotal / 3600) if ($lastMileElapsedTotal > 0); $CAN{MILES} = sprintf("%0.3f", $milesTotal); $CAN{TIME} = sprintf("%0.3f", $elapsedTotal); $CAN{MPHAVG} = sprintf("%0.3f", $mphAvg); $CAN{GALLONS} = sprintf("%0.3f", $gallonsTotal); $CAN{MPGAVG} = sprintf("%0.3f", $mpgAvg); if (($CAN{THROTTLE} < 5) && ($CAN{BATKW} < 0)) { $CAN{JOINTKW} = $CAN{JOINTKW} * -1; } $CAN{ICEKW} = $CAN{JOINTKW} - int($batkw); $CAN{ICEKW} = 0 if ($CAN{ICEKW} < 0); $CAN{ICEKW} = 0 if ($CAN{THROTTLE} == 0); if ($CAN{ICEKW} == 0){ if ($CAN{MG2RPM} == 0) { $CAN{MODE} = "STOP" } elsif ($CAN{BATKW} > 0) { $CAN{MODE} = "EV" } } if (($CAN{ICEKW} < 1) && ($CAN{ICERPM} > 0) && ($CAN{MPH} == 0)) { $CAN{MODE} = "ICE_WARM"; } if ($CAN{BATKW} < 0) { if (($CAN{BRAKE} > 0) && ($CAN{MPH} > 7)) { $CAN{MODE} = "REGEN_BRAKE" } elsif (($CAN{ICEKW} == 0) && ($CAN{MG2RPM} > 0)) { $CAN{MODE} = "REGEN_COAST" } elsif (($CAN{ICERPM} > 0) && ($CAN{MG2RPM} == 0)) { $CAN{MODE} = "STOP_CHARGE" } elsif (($CAN{BATKW} * -1) < $CAN{ICEKW}) { $CAN{MODE} = "ICE_MOT_CHARGE" } else { $CAN{MODE} = "ICE_CHARGE" } } elsif ($CAN{ICEKW} > 0){ if ($CAN{BATKW} == 0) { $CAN{MODE} = "ICE_MOT" } elsif ($CAN{BATKW} > 0) { $CAN{MODE} = "FULL" } } elsif (($CAN{ICEKW} == 0) && ($CAN{BATKW} > -1) && ($CAN{MG2RPM} > 0) && ($CAN{BATKW} < 1.3)) { $CAN{MODE} = "GLIDE" } $CAN{STAGE} = $lastStage; if ($CAN{ICETEMP} < 104) { $CAN{STAGE} = "S1"; } elsif ($CAN{ICETEMP} < 163.4) { $CAN{STAGE} = "S2"; } elsif (($CAN{STAGE} eq "S2") || ($CAN{STAGE} eq "S1") || (!$CAN{STAGE})) { $CAN{STAGE} = "S3a"; } elsif (($CAN{STAGE} =~ "S3") && ($CAN{MPH} >= 34) && ($CAN{STAGE} ne "S4")) { $CAN{STAGE} = "S3b"; } elsif (($CAN{STAGE} =~ "S3") && ($CAN{MPH} < 34) && ($CAN{MPH} > 0) && ($CAN{STAGE} ne "S4")) { $CAN{STAGE} = "S3a"; } elsif (($CAN{STAGE} =~ "S3") && ($CAN{MPH} == 0) && ($CAN{ICERPM} == 0)) { $CAN{STAGE} = "S4"; } else { $CAN{STAGE} = "S4"; } $lastStage = $CAN{STAGE}; } if (($CAN{JOINTKW} > 80) || ($CAN{SOC} > 85) || ($CAN{THROTTLE} > 100) || ($CAN{BRAKE} > 100) || ($CAN{MPH} > 120) || (($CAN{MPH} - $lastMph > 10) || ($CAN{MPH} - $lastMph < -10)) || (($CAN{SOC} - $lastSOC > 5) || ($CAN{SOC} - $lastSOC < -5))) { #print "Skipped Packet\n"; return(%CANOLD); } else { $lastMph = $CAN{MPH}; $lastSOC = $CAN{SOC}; #print "Good Packet\n"; if ($debug) { print "MPH/AVG: $CAN{MPH}/$CAN{MPHAVG}\tMPG/AVG: $CAN{MPG}/$CAN{MPGAVG}\tGallons: $CAN{GALLONS}\n"; print "Time: $CAN{TIME}\tMiles: $CAN{MILES}\n\n"; print "RPMs: ICE: $CAN{ICERPM}\tMG1: $CAN{MG1RPM}\tMG2: $CAN{MG2RPM}\n\n"; print "Current/Amps: $CAN{CURRENT}\n"; print "Volts: $CAN{VOLTS}\n"; print "SoC: $CAN{SOC}\n\n"; print "BatKW: $CAN{BATKW}\n"; print "ICEKW: $CAN{ICEKW}\n"; print "JointKW: $CAN{JOINTKW}\n\n"; print "Mode: $CAN{MODE}\n\n"; print "Stage: $CAN{STAGE}\tICE-Temp: $CAN{ICETEMP}\n\n"; print "Gear: $CAN{GEAR}\tThrottle: $CAN{THROTTLE}\tBrake: $CAN{BRAKE}\n\n"; } return(%CAN); } } # Touch screen Functions sub touchscreenScroll { my $pid = undef; return if ($pid = fork); die "fork: $!" unless defined $pid; print "= Touchscreen Scroll Child spawned ($$)\n" if ($debug > 1); $SIG{INT} = $SIG{TERM} = \&sig_handler; $SIG{PIPE} = 'IGNORE'; $SIG{CHLD} = \&REAPER; my $systemevents = new Mac::AppleScript::Glue::Application('System Events'); while(1) { if ($touchscreenScrollDirection eq "UP") { #$systemevents->key_code(126); RunAppleScript(qq(tell application "System Events"\nkey code(126)\nkey code(126)\nend tell)) } if ($touchscreenScrollDirection eq "DOWN") { #$systemevents->key_code(125); RunAppleScript(qq(tell application "System Events"\nkey code(125)\nkey code(125)\nend tell)) } } exit; } sub touchscreen { my @data = @_; my $BYTE = 0; $touchscreenHandle->shlock(); foreach (@data) { next if ($_[0] == 0); $BYTE = sprintf("%02X ", $data[0]); my ($x, $y) = split(//,$BYTE); $x = hex($x); $y = hex($y); print "$BYTE: $x,$y: " if ($debug); if (($y <= 5) && ($x > 10)) { if (!$touchscreenScrollDirection) { print "FrontRow Down Scroll Start\n" if ($debug); $touchscreenScrollDirection = "DOWN"; } else { $touchscreenScrollDirection = ""; } } elsif (($y > 10) && ($x > 10)) { if (!$touchscreenScrollDirection) { print "FrontRow Up Scroll Start\n" if ($debug); $touchscreenScrollDirection = "UP"; } else { $touchscreenScrollDirection = ""; } } else { fr_right() if (($x > 10) && ($y>5) && ($y<=10)); fr_left() if (($x <= 5) && ($y>5) && ($y<=10)); fr_down() if (($y <= 5) && ($x>5) && ($x<=10)); fr_up() if (($y > 10) && ($x>5) && ($x<=10)); fr_enter() if (($y <= 10) && ($y > 5) && ($x > 5) && ($x <=10)); fr_esc() if (($y > 10) && ($x < 5)); fr_start() if (($y <= 5) && ($x < 5)); $touchscreenScrollDirection = ""; } } $touchscreenHandle->shunlock(); } sub fr_right { print "FrontRow Right\n" if ($debug); $systemevents->key_code(124); } sub fr_left { print "FrontRow Left\n" if ($debug); $systemevents->key_code(123); } sub fr_up { print "FrontRow Up\n" if ($debug); $systemevents->key_code(126); } sub fr_down { print "FrontRow Down\n" if ($debug); $systemevents->key_code(125); } sub fr_enter { print "FrontRow Enter\n" if ($debug); $systemevents->key_code(36); } sub fr_esc { print "FrontRow Escape\n" if ($debug); $frontrow->activate; $systemevents->key_code(53); } sub fr_start { print "FrontRow Start\n" if ($debug); $frontrow->activate; $systemevents->key_code(53); usleep(5); $systemevents->key_code(53); } # Database Functions sub insertDatabase { my ($sec, $usec, $grabbed, $datagrabbed) = @_; %db = () if (scalar(keys %db) > 29); $db{"$sec.$usec"}{grabbed} = $grabbed; $db{"$sec.$usec"}{datagrabbed} = $datagrabbed; if (scalar(keys %db) > 29) { my $pid = undef; FORK: { if ($pid = fork) { # Parent here return; } elsif (defined $pid) { # Child here #$amChild = 1; # I AM A CHILD!!! } elsif ($! =~ /No more process/) { sleep 3; redo FORK; } else { return; } } # Only child processes get here. Parent returns. my $dbhc = DBI->connect("DBI:mysql:database=canview;host=localhost;mysql_connect_timeout=10", "canview", "CANVIEW", {'RaiseError'=>0}); my $sql = "INSERT INTO CANview VALUES (?,?,?,?)"; my $sth = $dbhc->prepare($sql) or die $dbhc->errstr; foreach (sort keys %db) { my ($sec, $usec, $grabbed, $datagrabbed) = (); if ($_ =~ /(^\d+)\.(\d+)/) { $sec = $1; $usec = $2; $grabbed = $db{$_}{grabbed}; $datagrabbed = $db{$_}{datagrabbed}; #print "DB: $sec $usec $grabbed\n"; $sth->execute(($sec,$usec,$grabbed,$datagrabbed)) or die $dbhc->errstr; } } $sth->finish(); $dbhc->disconnect or die $dbhc->errstr; exit; } } sub getRecordedData { my $where = ""; my $sql = "SELECT * from CANview $where order by epoch_sec,epoch_usec"; my $sth = $dbh->prepare($sql) or die $dbh->errstr; $sth->execute() or die $dbh->errstr; while (my $row = $sth->fetchrow_hashref()) { push(@rv,$row); } $sth->finish(); return(@rv); } # Process handler Functions sub sig_handler { print "Kill signal received... dying $$\n"; $handle->clean_up_all(); $touchscreenHandle->clean_up_all(); $dbh->disconnect or die $dbh->errstr; exit; } sub REAPER { my $pid; while (($pid = waitpid(-1, &WNOHANG)) > 0) { #print "Child on PID $pid is dead.\n"; } $SIG{CHLD} = \&REAPER; } sub daemonize { my $pid = fork; exit if $pid; die "Couldn't fork: $!" unless defined($pid); POSIX::setsid() or die "Can't start new session: $!"; $SIG{INT} = $SIG{TERM} = \&sig_handler; $SIG{PIPE} = 'IGNORE'; $SIG{CHLD} = 'IGNORE'; }