Perl/Tk OBD II interface
Post ReplyPerl/Tk OBD II interfacePosted: Monday, October 28, 2019 [13:06:49] - 1
Perl/Tk OBD II interface programPosted: Monday, October 28, 2019 [13:16:38] - 2
Program itself: ## Create a global hash with live data and update each gauge from the hash use strict; use warnings; use Tk; use Tk::Gauge; use Tk::Pane; use Device::ELM327; use subs qw/ getfilesdata readvalues tachometer speedometer temperature throttle intakemap igntiming loadvalue intaketemperature voltage /; $0 =~ m/(.+)[\/\\](.+)$/; unless(-d $progpath) {print "Please run this program with a fully qualified path\n"; my $localtime = `date`; our %readdata=(); my @parameters = ( # Parameters to read, changes with car make and model 'Engine Coolant Temperature', 'Absolute Throttle Position', 'Engine RPM', 'Calculated LOAD Value!', 'Bank 1 - Sensor 1', 'Bank 1 - Sensor 2', 'Intake Air Temperature', 'Vehicle Speed Sensor', 'Intake Manifold Absolute Pressure', 'Long Term Fuel Trim - Bank 1/3', 'Ignition Timing Advance for #1 Cylinder', 'Input Voltage' ); open(TXR,">$progpath/res/$mo.$da.$ti.txt"); ## Record all data to a plain text file my $debug_level = 0; # 0 to suppress debug output, >0 to print it. my $port_name='/dev/cu.usbserial'; my $obd = Device::ELM327->new($port_name, $debug_level); if ($obd->{'port'} == -1) {die "Can't open $port_name: $!\n";} my $mw = MainWindow->new; $mw->configure(-title=>"OBD II Live", -background=>"white"); $mw->Label(-text => 'OBD II Live',-font=>'OpenSans 22',-background =>'white')->pack(); #$mw->Button(-text => "Close", -command =>sub{exit})->pack(); my $mtop = $mw->Scrolled("Pane", -scrollbars => '')->pack( -fill => 'both', -expand => 1 ); $mtop->configure( -background => 'black' ); my $mbot = $mw->Scrolled("Pane", -scrollbars => '')->pack( -fill => 'both', -expand => 1 ); $mbot->configure( -background => 'black' ); my $tachf = $mtop->Frame->pack( qw/ -side left / ); my $speedf = $mtop->Frame->pack( qw/ -side left / ); my $tempf = $mtop->Frame->pack( qw/ -side left / ); my $cload = $mbot->Frame->pack( qw/ -side left / ); my $tint = $mbot->Frame->pack( qw/ -side left / ); my $throt = $mbot->Frame->pack( qw/ -side left / ); my $imap = $mbot->Frame->pack( qw/ -side left / ); my $ignt = $mbot->Frame->pack( qw/ -side left / ); my $volt = $mbot->Frame->pack( qw/ -side left / ); my $t = $tachf->Frame->pack( qw/ -side left / ); $t->Frame( qw/ -height 30 -background black / )->pack( qw/ -fill both -expand yes / ); tachometer ( $t )->pack( qw/ -side left / ); speedometer ( $speedf )->pack( qw/ -side left / ); my $tf = $tempf->Frame->pack( qw/ -side bottom / ); temperature ( $tf )->pack( qw/ -side left / ); my $trt = $throt->Frame->pack( qw/ -side left / ); throttle ( $trt )->pack( qw/ -side left / ); my $mapint = $imap->Frame->pack( qw/ -side left / ); intakemap ( $mapint )->pack( qw/ -side left / ); my $ignit = $ignt->Frame->pack( qw/ -side left / ); igntiming ( $ignit )->pack( qw/ -side left / ); my $calcld = $cload->Frame->pack( qw/ -side left / ); loadvalue ( $calcld )->pack( qw/ -side left / ); my $tempintake = $tint->Frame->pack( qw/ -side left / ); intaketemperature ( $tempintake )->pack( qw/ -side left / ); my $svolt = $volt->Frame->pack( qw/ -side left / ); voltage ( $svolt )->pack( qw/ -side left / ); $mw->repeat(1500 => \&run); # It appears that value below 1500 (1.5 second) may not work as Tk can't react as fast MainLoop; sub run { readvalues; $mw->update; } ## END SUB RUN sub readvalues { my @reads = @parameters; # Convert names to something shorted to display in a program window my %conv = ('Engine Coolant Temperature','ECT', 'Absolute Throttle Position','Throttle', 'Engine RPM','RPM', 'Calculated LOAD Value!','LOAD', 'Sensor 1 Oxygen Sensor Output Voltage (Bx-Sy)','STFT V 1', 'Sensor 1 Short Term Fuel Trim (Bx-Sy)','STFT 1', 'Sensor 2 Oxygen Sensor Output Voltage (Bx-Sy)','STFT V 2', 'Sensor 2 Short Term Fuel Trim (Bx-Sy)','STFT 2', 'Intake Air Temperature','Intake Temp', 'Vehicle Speed Sensor','Speed', 'Intake Manifold Absolute Pressure','Int. MAP', 'Bank 1/3 Long Term Fuel Trim - Bank 1','LTFT 1', 'Bank 1/3 Long Term Fuel Trim - Bank 3','LTFT 3', 'Ignition Timing Advance for #1 Cylinder','Ign. Timing', 'Input Voltage','V' ); foreach my $p (@reads) { my $resp = readobd($obd,$p); my @alln = split(/\n/,$resp); my $p1=$p; foreach my $rvlu (@alln) { $rvlu =~ s#(.*?)\:(.*)#$1#; my $hkey = "$p1 $rvlu"; my $rke = ''; print TXR "$p\t$hkey\t$rke\t$val\n"; ## Checking that data is processed - it is processed properly if($val < 0 && $val) {$val =~ s/^\-//;} $readdata{$rke}=$val; ## Hash changes properly } ## FOREACH VALUE END } ## FOREACH PARAMETER END return (%readdata); } ## END READ VALUES sub readobd { my ($ffrom,$senssor) = @_; my $obres = $ffrom->Read($senssor); if ($obres->{'status'} eq "ok") { foreach my $obread (@{$obres->{'results'}}) { $obdprint .= "$obread->{'name'}:$obread->{'value'}\n"; } } else { $obdprint .= "$obres->{'status'}\n"; } return($obdprint); } ## END DISPLAY VALUES sub setwindow { my $window = shift; my $width = $window->screenwidth - 40; my $height = $window->screenheight - 100; $window->geometry($width . "x" . $height . "+20+30"); } sub tachometer { my $c = shift; my $iLCD = 50; # integral Lowest Common Denominator my $tachv; my $tach = $c->Gauge( -start => 225, -extent => -270, -background => 'black', -bands => [ { -arccolor => 'red', -minimum => 6100 / $iLCD, -maximum => 8000 / $iLCD, -tags => 'redtach', }, { -arccolor => '#f4f118', -minimum => 4500 / $iLCD, -maximum => 6100 / $iLCD, -tags => 'yeltach', }, { -arccolor => 'red', -minimum => 0 / $iLCD, -maximum => 600 / $iLCD, -tags => 'redtach', }, ], -bandwidth => 4, -bandstyle => 'band', -bandplace => 'underticks', -fill => '#343434', -finetickinterval => 250 / $iLCD, -fineticklength => 5, -finetickcolor => 'white', -from => 0, -highlightthickness => 0, -hubcolor => '#aeaeaf', -huboutline => 'black', -hubplace => 'underneedle', -hubradius => 15, -majortickinterval => 1000 / $iLCD, -majorticklength => 15, -majortickcolor => 'white', -majorticklabelcolor => 'white', -majorticklabelscale => $iLCD / 1000, -margin => 60, -minortickinterval => 500 / $iLCD, -minorticklength => 10, -minortickcolor => 'white', -needles => [ { -radius => 120, -color => 'red', -command => [ sub { # print "tach args=@_!\n"; 1; }, 1, 2 ], -variable => \$tachv, -width => 4, -showvalue => 0, -title => 'x 1000 RPM', -titlecolor => 'white', -titleplace => 'south', -arrowshape => [ 6, 6, 0 ], }, ] , -needlepad => 15, -to => 8000 / $iLCD, ); $mw->repeat(100 => sub { if ($readdata{'RPM'}) { $tachv = $readdata{'RPM'} / $iLCD;} else {$tachv = 80 / $iLCD;} } ); return $tach; } # end tachometer sub speedometer { my $c = shift; my $font = 'Helvetica 20'; my $radius = 80; my $speedv; my $speedvk; my $speed = $c->Gauge( -background => 'black', -fill => '#343434', -bands => [ { -arccolor => '#46a317', -minimum => 55, -maximum => 65, }, { -arccolor => '#faf704', -minimum => 65, -maximum => 75, }, { -arccolor => '#cdcdcd', -minimum => 75, -maximum => 145, }, ], -bandwidth => 2, -bandstyle => 'band', -bandplace => 'underticks', -finetickinterval => 5, -fineticklength => 5, -finetickcolor => 'white', -from => 0, -highlightthickness => 0, -hubcolor => '#aeaeaf', -huboutline => 'black', -hubplace => 'underneedle', -hubradius => 20, -majortickinterval => 20, -majorticklength => 15, -majortickcolor => 'white', -majorticklabelcolor => 'white', -minortickinterval => 10, -minorticklength => 15, -margin => 30, -minortickcolor => 'white', -needles => [ { -radius => 140, -color => '#3a7efb', -variable => \$speedvk, -width => 3, -showvalue => 0, -arrowshape => [ 6, 6, 0 ], }, { -radius => 160, -color => 'orangered2', -variable => \$speedv, -width => 3, -showvalue => 0, -title => 'MpH / KmH', -titlecolor => 'white', -titleplace => 'south', -arrowshape => [ 6, 6, 0 ], }, ] , -needlepad => 15, -to => 150, ); $mw->repeat(1000 => sub { if ($readdata{'Speed'}) {$speedvk=$readdata{'Speed'}; $readdata{'Speed'} /= 1.60934; $speedv = sprintf("%0.0f",$readdata{'Speed'});} else {$speedv = 0; $speedvk=0;} } ); return $speed; } # end speedoemter sub temperature { my $c = shift; my $font = 'Helvetica -20'; my $tempv; my $radius = 80; my $ticks = 36; my $temp = $c->Gauge( -background => 'black', -bands => [ { -arccolor => 'red', -minimum => 26, -maximum => 36, }, { -arccolor => 'green', -minimum => 23, -maximum => 26, }, { -arccolor => 'yellow', -minimum => 19, -maximum => 23, }, { -arccolor => '#5d98fa', -minimum => 1, -maximum => 19, }, ], -bandwidth => 10, -bandstyle => 'band', -bandplace => 'underticks', -extent => -90, -fill => '#343434', -from => 0, -highlightthickness => 0, -hubcolor => '#aeaeaf', -huboutline => 'black', -hubplace => 'underneedle', -hubradius => 20, -majortickinterval => 12, -majorticklength => 14, -majortickcolor => 'white', -majorticklabelcolor => 'white', -majorticklabelplace => 'hide', -majorticklabelscale => .5, -majortickthickness => 3, -minortickinterval => 4, -minortickcolor => 'white', -minorticklength => 9, -margin => 30, -needles => [ { -radius => $radius, -color => 'orangered2', -variable => \$tempv, -width => 3, -showvalue => 0, -arrowshape => [ 6, 6, 0 ], }, ] , -to => $ticks, -start => 180, -style => 'pieslice', ); my( $hx, $hy ) = $temp->centerpoint; my $fw = $temp->fontMeasure( $font, '0' ); $temp->createText( $hx + $fw , $hy - $radius, -text => 'H', -fill => 'white', -font => $font ); $temp->createText( $hx - $radius , $hy + $fw, -text => 'C', -fill => 'white', -font => $font ); $mw->repeat(1000 => sub { if ($readdata{'ECT'}) {$tempv = $readdata{'ECT'} * 0.25;} else {$tempv = 0;} } ); return $temp; } # end temperature sub throttle { my $c = shift; my $font = 'Helvetica -20'; my $throttle; my $combi = $c->Gauge( -start => -extent => -120, -background => 'black', -fill => '#343434', -finetickinterval => 5, -fineticklength => 5, -finetickcolor => 'white', -from => 0, -highlightthickness => 0, -hubcolor => 'black', -huboutline => 'black', -hubplace => 'underneedle', -hubradius => 20, -majortickinterval => 20, -majorticklength => 15, -majortickcolor => 'white', -majorticklabelcolor => 'white', -minortickinterval => 5, -minortickcolor => 'white', -minorticklength => 9, -margin => 10, -needles => [ { -radius => 75, -color => '#dc9d05', -variable => \$throttle, -width => 3, -showvalue => 0, -arrowshape => [ 6, 6, 0 ], }, ] , -needlepad => 10, -to => 80, -style => 'pieslice', ); $combi->createText( 80 , 40, -text => 'Throttle', -fill => 'white', -font => $font ); $mw->repeat(1000 => sub { if($readdata{'Throttle'}) {$throttle = sprintf("%0.0f",$readdata{'Throttle'});} else {$throttle = 0;} } ); return $combi; } # end combination sub intakemap { my $c = shift; my $font = 'Helvetica -20'; my $throttle; my $load; my $inttemp; my $map; my $timing; my $voltage; my $radius = 80; my $iimap = $c->Gauge( -start => -extent => -120, -background => 'black', -fill => '#343434', -finetickinterval => 5, -fineticklength => 5, -finetickcolor => 'white', -from => 0, -highlightthickness => 0, -hubcolor => 'black', -huboutline => 'black', -hubplace => 'underneedle', -hubradius => 20, -majortickinterval => 40, -majorticklength => 15, -majortickcolor => 'white', -majorticklabelcolor => 'white', -minortickinterval => 5, -minortickcolor => 'white', -minorticklength => 9, -margin => 10, -needles => [ { -radius => 75, -color => '#fdf403', -variable => \$map, -width => 3, -showvalue => 0, -arrowshape => [ 6, 6, 0 ], }, ] , -needlepad => 10, -to => 120, -style => 'pieslice', ); $iimap->createText( 90 , 40, -text => 'Intake MAP', -fill => 'white', -font => $font ); $mw->repeat(1000 => sub { if($readdata{'Int. MAP'}) {$map = $readdata{'Int. MAP'}; } else {$map = 0;} } ); return $iimap; } ## END SUB Intake Manifold Absolute Pressure sub igntiming { my $c = shift; my $font = 'Helvetica -20'; my $timing; my $radius = 80; my $itiming = $c->Gauge( -start => -extent => -120, -background => 'black', -fill => '#343434', -finetickinterval => 5, -fineticklength => 5, -finetickcolor => 'white', -from => 20, -highlightthickness => 0, -hubcolor => 'black', -huboutline => 'black', -hubplace => 'underneedle', -hubradius => 20, -majortickinterval => 20, -majorticklength => 15, -majortickcolor => 'white', -majorticklabelcolor => 'white', -minortickinterval => 5, -minortickcolor => 'white', -minorticklength => 9, -margin => 10, -needles => [ { -radius => 75, -color => '#0623fb', -variable => \$timing, -width => 3, -showvalue => 0, -arrowshape => [ 6, 6, 0 ], }, ] , -needlepad => 10, -to => 80, -style => 'pieslice', ); $itiming->createText( 80 , 40, -text => 'Ign. Timing', -fill => 'white', -font => $font ); $mw->repeat(1000 => sub { if($readdata{'Ign. Timing'}) {$timing = $readdata{'Ign. Timing'}; print "$timing\n"; } else {$timing = 0;} } ); return $itiming; } ## END IGNITION TIMING sub loadvalue { my $c = shift; my $font = 'Helvetica -20'; my $load; my $radius = 80; my $combi = $c->Gauge( -start => -extent => -120, -background => 'black', -fill => '#343434', -finetickinterval => 5, -fineticklength => 5, -finetickcolor => 'white', -from => 0, -highlightthickness => 0, -hubcolor => 'black', -huboutline => 'black', -hubplace => 'underneedle', -hubradius => 20, -majortickinterval => 20, -majorticklength => 15, -majortickcolor => 'white', -majorticklabelcolor => 'white', -minortickinterval => 5, -minortickcolor => 'white', -minorticklength => 9, -margin => 10, -needles => [ { -radius => 75, -color => '#fa915d', -variable => \$load, -width => 3, -showvalue => 0, -arrowshape => [ 6, 6, 0 ], }, ] , -needlepad => 10, -to => 100, -style => 'pieslice', ); $combi->createText( 90 , 40, -text => 'Load', -fill => 'white', -font => $font ); $mw->repeat(1000 => sub { if($readdata{'LOAD'}) {$load = sprintf("%0.0f",$readdata{'LOAD'}); } else {$load = 0;} } ); return $combi; } # end Load sub intaketemperature { my $c = shift; my $font = 'Helvetica -20'; my $inttemp; my $radius = 80; my $combi = $c->Gauge( -start => -extent => -120, -background => 'black', -fill => '#343434', -finetickinterval => 5, -fineticklength => 5, -finetickcolor => 'white', -from => 0, -highlightthickness => 0, -hubcolor => 'black', -huboutline => 'black', -hubplace => 'underneedle', -hubradius => 20, -majortickinterval => 20, -majorticklength => 15, -majortickcolor => 'white', -majorticklabelcolor => 'white', -minortickinterval => 5, -minortickcolor => 'white', -minorticklength => 9, -margin => 10, -needles => [ { -radius => 75, -color => '#5dfa73', -variable => \$inttemp, -width => 3, -showvalue => 0, -arrowshape => [ 6, 6, 0 ], }, ] , -needlepad => 10, -to => 100, -style => 'pieslice', ); $combi->createText( 100 , 40, -text => 'Intake Temp', -fill => 'white', -font => $font ); $mw->repeat(1000 => sub { if($readdata{'Intake Temp'}) {$inttemp = $readdata{'Intake Temp'}; } else {$inttemp = 0;} } ); return $combi; } ## END SUB INTAKE TEMPERATURE sub voltage { my $c = shift; my $font = 'Helvetica -20'; my $voltage; my $radius = 80; my $combi = $c->Gauge( -start => -extent => -120, -background => 'black', -fill => '#343434', -bands => [ { -arccolor => '#fa1924', -minimum => 10, -maximum => 12, }, { -arccolor => '#f6f917', -minimum => 12, -maximum => 12.8, }, { -arccolor => '#33bd03', -minimum => 13, -maximum => 15, }, ], -bandwidth => 2, -bandstyle => 'band', -bandplace => 'underticks', -finetickinterval => 5, -fineticklength => 5, -finetickcolor => 'white', -from => 10, -highlightthickness => 0, -hubcolor => 'black', -huboutline => 'black', -hubplace => 'underneedle', -hubradius => 20, -majortickinterval => 5, -majorticklength => 15, -majortickcolor => 'white', -majorticklabelcolor => 'white', -minortickinterval => 1, -minortickcolor => 'white', -minorticklength => 9, -margin => 10, -needles => [ { -radius => 75, -color => '#5d98fa', -variable => \$voltage, -width => 3, -showvalue => 0, -arrowshape => [ 6, 6, 0 ], }, ] , -needlepad => 10, -to => 20, -style => 'pieslice', ); $combi->createText( 100 , 40, -text => 'V', -fill => 'white', -font => $font ); $mw->repeat(1000 => sub { if($readdata{'V'}) {$voltage = $readdata{'V'}; } else {$voltage = 0;} } ); return $combi; } ## END SUB VOLTAGE program shows positive numbers for Ignition timing while in-fact they're negative numbers. Also, it shows speed in MpH (red needle) and Kph (blue needle). unfortunately refresh ratio is about 1.5 seconds. Any number below 1.5 seconds renders dead GUI. It may depend on interface reading speed or Tk rendering. |