Ein einfacher Yacht-Navigator


Systemvoraussetzungen:


Hardware:

Software:
  • Betriebssystem: LINUX (empfohlen UBUNTU, notfalls WINDOWS)
  • Perl
  • Das hier beschriebene Programm 'navi.pl'
  • System- und Programmiererfahrung in Perl und Linux !
    
In Ubuntu müssen noch folgende Zusatzprogramme installiert werden:
  • socat
  • cpulimit
  • wine1.7 oder höher
    
Mit dem Befehl 'sudo apt-get install <Programmname>' ist dies leicht zu bewerkstelligen.
Diese Zusatzprogramme sind optional und werden nur benötigt, wenn gleichzeitig ein Kartenplotter (SeaClear o.ä.) auf dem Notebook betrieben werden soll.

Perl ist in den LINUX-Distributionen bereits enthalten, jedoch müssen folgende Module nachinstalliert werden:
  • Tk
  • Tk::Dialog
  • Device::SerialPort
  • DateTime
  • Thread
  • Time::HiRes
    
Dies ist relativ einfach und geschieht mit dem Befehl 'cpanm <Modulname>' als Root.
Sollte cpanm noch nicht installiert sein: 'sudo apt-get install cpanm'
Das Perl-Modul Tk wird mit dem Befehl 'sudo apt-get install perl-tk' installiert.
    
Folgende Navigationsinformationen werden angezeigt:
Bild "/kategorien/Projekte/dateien/navigauge.png"
  • Uhrzeit (MEZ) mit automatischer Umstellung Winter- und Sommerzeit
  • Datum
  • Anzahl der empfangenen GPS-Satelliten
  • Qualität der Positionsbestimmung
  • Echtzeit-Kompass
  • Kurs über Grund (COG)
  • Position
  • Geschwindigkeit über Grund (SOG)
    
Es besteht die Möglichkeit der Helligkeitssteuerung des Bilschirmes (wichtig bei Nachtfahrten) und des Startens eines zusätzlichen Kartenplotter-Programms z.B. SeaClear oder OpenCPN.
Hierzu wird kein zusätzlicher GPS-Empfänger und auch keine zusätzliche Schnittstelle benötigt. Wie dies geschieht ist in Tips & Tricks beschrieben.

Das Listing zeigt das Programm 'navi.pl':

#!/usr/bin/perl -w
use warnings;
use strict;
use Tk;
use Tk::Dialog;
use utf8;
use Device::SerialPort;
use DateTime ;
use Thread;
use Time::HiRes qw(usleep nanosleep);
my $cpulimit = 25;
my $thread1;
my $thrun : shared = 0;
my $usbvsend ="/dev/ttyUSB98";
my $usbvread ="/dev/ttyUSB99";#Link zu .wine COM3 in wine
my $lsusb="";
my $pos = "Position";
my $center_x=250;
my $radius=300;
my $center_y=360;
my $i=0;
my $n = 0;
my $kursalt=10;
my $kurs=0;
my $move=1;
my $seaclear='/media/NAS/Dokumente/Hannes/Boot/SeaClear/SeaClear_2.exe';
my ($drawpos,$drawlog,$drawcompass,$drawmenu,$ob,$obv,$socatpid,$log,$time,$date,$sats,$brightness,
    @br,$scale,$startsc,$quit);
my ($mw,$readgps,$count,$dialog);
$ob = Device::SerialPort->new("/dev/ttyUSB0");

&initwindow;
&readserial;
MainLoop;

sub initwindow{
        $mw = new MainWindow();
        $mw->configure (-width=>"525",-height=>"500", -background=>'black');
        $mw->packPropagate(0);
                        
        $drawcompass = $mw->Canvas(-width => 500, -height => 210, -background=>"black",-relief => 'ridge',borderwidth=>8) -> pack;
        #Kompassmarke
        $drawcompass->createPolygon($center_x, $center_y-$radius-5,$center_x-10, $center_y-$radius-30,
                $center_x+10, $center_y-$radius-30,-outline =>'white',-fill=>'red',);
        #Kompassbogen
        $drawcompass->createArc($center_x - $radius, $center_y - $radius,$center_x + $radius, $center_y + $radius,
                -extent => -94,-start => 137,-outline => 'white',-width   => 4,-style => 'arc',);
        #Datum und Zeit
        $drawcompass->createText( $center_x-220,$center_y-330,-fill =>  'white',-text => 'Time',-font => 'Arial 12',-anchor => 'w',-tags=>'time');
        $drawcompass->createText( $center_x-220,$center_y-310,-fill =>  'white',-text => 'Date',-font => 'Arial 12',-anchor => 'w',-tags=>'date');
        #Satelliten
        $drawcompass->createText( $center_x+150,$center_y-330,-fill =>  'white',-text => 'Satelliten:',-font => 'Arial 12',-anchor => 'w',-tags=>'sats');
        $drawcompass->createText( $center_x+150,$center_y-310,-fill =>  'white',-text => 'Qualität:',-font => 'Arial 12',-anchor => 'w',-tags=>'quality');
        
        $drawpos = $mw->Canvas(-width => 500, -height => 120, -background=>"black",-relief => 'ridge',-borderwidth=>8) -> pack;
        $drawlog = $mw->Canvas(-width => 500, -height => 80, -background=>"black",-relief => 'ridge',-borderwidth=>8) -> pack;
        $drawpos->createText( $center_x, $center_y - $radius*0.9,-fill =>  'white',-text => $pos,-font => 'Arial 24',-tags => 'posi',);
        $drawpos->createText( $center_x, $center_y - $radius*1.05,-fill =>  'white',-text => "Kurs: " . $kurs ." °",-font => 'Arial 24',-tags => 'kurs',);
        &showlog;
        
        $drawmenu = $mw->Canvas(-width => 500, -background=>"black",-relief => 'solid',-borderwidth=>1) -> pack;
        
        #Helligkeitsregler
        $scale=$drawmenu->Scale(-from => 100, -to => 10, -orient        => 'horizontal', -background => 'black', -troughcolor=>  'black', -showvalue => 0,
                -length => 374,        -command=>\&setbrightness,        )->pack(-side => 'left',-expand => 1);
        $scale->set(100);#Helligkeit auf 100%
        
        #Startbutton SeaClear
        $startsc = $drawmenu->Button(-text => 'SeaClear',-background => 'black',-font => 'Arial 10',-foreground => 'white',-command=>\&seaclear,
                )->pack(-side => 'left',-expand => 1);
        #Exit-Button
        $quit = $drawmenu->Button(-text => 'Quit',-background => 'black',-font => 'Arial 10',-foreground => 'white',-command=>\&quit,
                )->pack(-side => 'left',-expand => 1);
        #Dialog
        
        $dialog = $mw->Dialog(-title => 'Fehlermeldung!',-text => 'Keine GPS-Signal gefunden!',-font => 'Arial 11 bold',
                -background => 'red',-foreground => 'white',-buttons => ['Ok'],);
        
        
        
        $mw->update;
        $mw->protocol(WM_DELETE_WINDOW => \&quit);
        
        $readgps = 1;
        $count = 0;

}
sub initsocat{
        if (qx(pidof socat) ne ''){return}
        my $thread2=threads->new(\&startsocat);
        #Prüfen ob ttyUSB99 vorhanden ist
        do {
                $lsusb = qx(ls $usbvread 2>&1);
                chomp($lsusb);
                $n++;
                }
        until ($lsusb eq $usbvread);
        #print $lsusb;
        system("/usr/bin/sudo /bin/chmod 777 ".$usbvsend);
        system("/usr/bin/sudo /bin/chmod 777 ".$usbvread);
        
        $obv = Device::SerialPort->new($usbvsend);
        $obv->baudrate(4800);
        $obv->parity('none');
        $obv->databits(8);
        $obv->stopbits(1);
        $obv->handshake('none');
        $obv->write_settings or die "no settings\n";
}
sub startsocat{
        system("/usr/bin/sudo /usr/bin/socat pty,link=".$usbvsend.",raw,b4800 pty,link=".$usbvread.",raw,b4800");
}

sub readserial{
        my $s1=qx(ls /dev/ttyUSB0 2>&1);
        chomp($s1);
        if($s1 ne '/dev/ttyUSB0'){
                &msgbox;
                return;
        }
        system('sudo chmod 666 /dev/ttyUSB0');
        my $posid;
        my $kursid;
        $log = 0;
        
        $ob->baudrate(4800);
        $ob->parity('none');
        $ob->databits(8);
        $ob->stopbits(1);
        $ob->handshake('none');
        $ob->write_settings or die "no settings\n";
        
        my $charcount = 0;
        my $line = "";
        my $lat = "";
        my $lon = "";
        my $cog = 0;
        my $result="";
        while($readgps == 1){
                my ($count,$result) = $ob->read(1);
                $charcount += $count;
            if ($result ne "\n"){
                    $line = $line . $result;
            }
            else {
                    my $sc=qx(pidof socat);
                    $obv->write($line) if ($sc ne "");
                    
                    if (index($line,"GPRMC") > 0){
                            
                            my @koords=split(/,/,$line);
                            $koords[3] =~ s/\./,/;
                            $koords[5] =~ s/\./,/;
                            $lat = substr($koords[3],0,2) . " ° " . substr($koords[3],2,5);
                            $lon = substr($koords[5],0,3) . " ° " . substr($koords[5],3,5);
                            $lat = $lat . " " . $koords[4];
                            $lon = $lon . " " . $koords[6];
                                $cog = $koords[8];
                                $log= $koords[7];
                                $time=$koords[1];
                    }
                    #Datum und Zeit aktualisieren
                    if (index($line,"GPZDA") > 0){
                            &showdate($line);
                    }
                    #Anzahl Satelliten
                    if (index($line,"GPGGA") > 0){
                            &showsats($line);
                    }
                    $pos = $lat . "     " . $lon;
                    $kurs = int($cog);
                    $line = "";
                    #Kurs aktualisieren
                        $drawpos->dchars('kurs',0,10);
                    $drawpos->insert('kurs',0, 'Kurs : ' . $kurs . '°');
                    #Position aktualisieren
                    $drawpos->dchars('posi',0,50);
                    $drawpos->insert('posi',0, $pos);
                    #Kompass aktualisieren
                    if($move==1){&movecompass($kurs)};
                    #LOG aktualisieren
                    if($log > 0){
                            &movelog($log);
                    };
                    #SeaClear-Button aktualisieren
                    if($move==1){
                            my $sc=qx(pidof $seaclear);
                            if ($sc ne ""){$startsc->configure(-text=>'Running',-background => 'green');}
                            else{$startsc->configure(-text=>'SeaClear',-background => 'black')}
                    }
                    select(undef, undef, undef, 0.3);
                    $mw->update;
                    }
         }
}
sub movecompass{
                my $kurs = shift;
                if ($kurs == $kursalt){return}
                if ($kurs > $kursalt){
                        if(abs($kurs-$kursalt)<=180){
                                for ($n=$kursalt;$n<=$kurs;$n++){
                                        if($move==1){
                                                &clearcompass ;
                                                &showcompass($n);
                                                $mw->update;
                                        }
                                }
                        }
                        else{
                                for ($n=$kursalt;$n>=$kurs-360;$n--){
                                        if($move==1){
                                                &clearcompass;
                                                &showcompass($n);
                                                $mw->update;
                                        }
                                }
                        }
                }
                else{
                        if(abs($kurs-$kursalt)<180){
                                for ($n=$kursalt;$n>=$kurs;$n--){
                                        if($move==1){
                                                &clearcompass;
                                                &showcompass($n);
                                                $mw->update;
                                        }
                                }
                        }
                        else{
                                
                                for ($n=$kursalt;$n<=360+$kurs;$n++){
                                        if($move==1){
                                                &clearcompass;
                                                my $k = $n;
                                                if($n>=360){$k=$n-360}
                                                &showcompass($k);
                                                $mw->update;
                                        }
                                }
                        }
                }
                $mw->update if($move==1);
                $kursalt = $kurs;
}


sub showcompass{
        if($move==0){return}
        my $kurs = shift;
        my $text;
        my $range=46;        
        for ($i=$kurs-$range;$i<=$kurs+$range;$i++) {
                my $numeralRadius = 0.85 * $radius;
                my $angle = $i -90 - $kurs;
            $angle = $angle * 3.14159 / 180  ;
        my $x = $center_x +cos($angle)*$numeralRadius ;
            my $y = $center_y + sin($angle)*$numeralRadius ;
                
                if ($i % 10 == 0){
                        if ($i < 0){$text= 360 + $i}
                        elsif ($i > 360){$text= $i - 360}
                        else{$text = $i}
                        $drawcompass->createText( $x, $y,-fill =>  'white',-text => $text,-font => 'Arial 12',-tags=>'compmoves');
                }
                if ($i % 45 == 0){
                        $x = $center_x + cos($angle)*($numeralRadius * 0.85);
                    $y = $center_y + sin($angle)*($numeralRadius * 0.85);
                        if($i==0||$i==360){$drawcompass->createText( $x, $y,-fill =>  'white',-text => "N",-font => 'Arial 24',-tags=>'compmoves');}
                        if($i==45||$i==405){$drawcompass->createText( $x, $y,-fill =>  'white',-text => "NO",-font => 'Arial 16',-tags=>'compmoves');}
                        if($i==90){$drawcompass->createText( $x, $y,-fill =>  'white',-text => "O",-font => 'Arial 24',-tags=>'compmoves');}
                        if($i==135){$drawcompass->createText( $x, $y,-fill =>  'white',-text => "SO",-font => 'Arial 16',-tags=>'compmoves');}
                        if(abs($i)==180){$drawcompass->createText( $x, $y,-fill =>  'white',-text => "S",-font => 'Arial 24',-tags=>'compmoves');}
                        if($i==225||$i==-135){$drawcompass->createText( $x, $y,-fill =>  'white',-text => "SW",-font => 'Arial 16',-tags=>'compmoves');}
                        if($i==270||$i==-90){$drawcompass->createText( $x, $y,-fill =>  'white',-text => "W",-font => 'Arial 24',-tags=>'compmoves');}
                        if($i==315||$i==-45){$drawcompass->createText( $x, $y,-fill =>  'white',-text => "NW",-font => 'Arial 16',-tags=>'compmoves');}
                }
                        #Kleine Teilstriche
                if ($i % 2 == 0){
                    $x = $center_x + cos($angle)*($numeralRadius * 1.13);
                    $y = $center_y + sin($angle)*($numeralRadius * 1.13);
                    my $x2 = $center_x +cos($angle)*($radius) ;
                    my $y2 = $center_y + sin($angle)*($radius);
                        $drawcompass->createLine( $x, $y,  $x2 , $y2 , -fill  => 'white',-width => 2,,-tags=>'compmoves');
            }
            #Große Teilstriche
            if ($i % 10 == 0){
                    $x = $center_x + cos($angle)*($numeralRadius * 1.1);
                    $y = $center_y + sin($angle)*($numeralRadius * 1.1);
                    my $x2 = $center_x +cos($angle)*($radius) ;
                    my $y2 = $center_y + sin($angle)*($radius);
                        $drawcompass->createLine( $x, $y,  $x2 , $y2 , -fill  => 'white', -width => 4,,-tags=>'compmoves');
            }
        }
        
}
sub movelog{
        if($move==0){return}
        
        my $log = shift;
        $log =($log*40)+16;
        my $y = 50;
        my $y2 = 80;
        $drawlog->delete('logzeiger');
        $drawlog->createLine( $log, $y-30, $log , $y2,-fill  => 'red', -width => 4,-tags=>'logzeiger');
}
sub showlog{
        my $breite = 2 * $center_x; #(500)
        my $range=120;
        my $bigstep = int($breite/$range);
        my $x = 4 * $bigstep;
        my $y = 50;
        my $y2 = 80;
        $drawlog->createText( $center_x+6, $y2-40, -fill => 'white', -text => 'Speed over ground (kn)', -font => 'Arial 9', );
        for ($i=0;$i<=$range;$i++){
                if($i % 10 == 0){
                        $drawlog->createLine( $x+$i*$bigstep, $y, $x+$i*$bigstep , $y2,-fill  => 'white', -width => 3,);
                        $drawlog->createText( $x+$i*$bigstep, $y2-55, -fill => 'white', -text => $i/10, -font => 'Arial 12', -tags => 'log',);
                        }
                if($i % 5 == 0){
                        #$drawlog->createText( $x+$i*$bigstep, $y2-25, -fill => 'white', -text => 5, -font => 'Arial 8', -tags => 'log',);
                        $drawlog->createLine( $x+$i*$bigstep, $y +10 , $x+$i*$bigstep , $y2,-fill  => 'white', -width => 1,);
                        }
                $drawlog->createLine( $x+$i*$bigstep, $y +20 , $x+$i*$bigstep , $y2,-fill  => 'white', -width => 1,);                
                }
        $drawlog->createLine( $x, $y-30, $x , $y2,-fill  => 'red', -width => 4,-tags=>'logzeiger');
        
}
sub showdate{
        my $dt = shift;
        my $mez;
        my $datetime;
        my @datetime=split(/,/,$dt);
        if ($datetime[4] ne "" and length($datetime[4])==4){
        #Beginn Sommerzeit letzter Sonntag im März
                $date = DateTime->last_day_of_month( year => $datetime[4] , month => 3 ) ;
                while ( $date->dow != 7 ) {
                    $date = $date->subtract( days => 1 ) ;
                }
                my $sobegin = $date->ymd;
                #Ende Sommerzeit letzter Sonntag im Oktober
                $date = DateTime->last_day_of_month( year => $datetime[4] , month => 10 ) ;
                while ( $date->dow != 7 ) {
                    $date = $date->subtract( days => 1 ) ;
                }
                my $soend = $date->ymd;
                $date=$datetime[4]."-".$datetime[3]."-".$datetime[2];
                #Ist Sommerzeit?
                if ($date ge $sobegin and $date le $soend){
                        $mez=(substr($datetime[1],0,2))+2;
                        }
                else{
                        $mez=(substr($datetime[1],0,2))+1;
                        }
                $date=$datetime[2] . "." . $datetime[3] . "." . $datetime[4];
                $time=$mez.":".substr($datetime[1],2,2).":".substr($datetime[1],4,2);
                #Aktualisierung
                $drawcompass->dchars('date',0,50);
                $drawcompass->insert('date',0,$date);
                $drawcompass->dchars('time',0,50);
                $drawcompass->insert('time',0,$time.' MEZ');
        }
}
sub showsats{
        my $st = shift;
        my @sats=split(/,/,$st);
        if ($sats[7] =~ /\d+?$/) {
                $drawcompass->dchars('sats',0,50);
                $drawcompass->insert('sats',0,'Satelliten: '.$sats[7]);
                $drawcompass->dchars('quality',0,50);
                $drawcompass->insert('quality',0,'Qualität: '.$sats[6]);
        }
}
sub seaclear{
        my $sc=qx(pidof $seaclear);
        print $sc;
        if ($sc ne ""){
                qx(kill -9 $sc);
                $sc=qx(pidof wineserver);
                qx(kill -9 $sc);
                $sc=qx(pidof socat);
                qx(sudo kill -9 $sc);
        }
        else{
                &initsocat;
                $thread1=threads->new(\&startseaclear);
                my $thread3=threads->new(\&startcpulimit);
                }
}
sub startseaclear{
        qx(wine $seaclear 2>&1);
        }
sub startcpulimit{
        my $pidwineserver = '';
        while($pidwineserver eq ''){
                $pidwineserver = qx(pidof wineserver);
                if ($pidwineserver ne ''){
                        system('cpulimit -l '.$cpulimit.' -b -p '.$pidwineserver);
                }
        sleep(1);
        }
}
sub clearcompass{
        if($move==1){
                $drawcompass->delete('compmoves');
        }
}
sub setbrightness{
        $brightness = ($scale->get())*0.01;
        my $br = qx(xrandr -q | grep " connected");
        @br=split(/\n/,$br);
        my $screen0 = $br[0];
        my $screen1 = $br[1];
        @br=split(/ /,$screen0);
        $screen0 =$br[0];
        if($screen1 ne ""){@br=split(/ /,$screen1)}
        $screen1 =$br[0];
        if($screen1 ne ""){
                $br = qx(xrandr --output $screen1 --brightness $brightness);
        }
        $br = qx(xrandr --output $screen0 --brightness $brightness);
}
sub msgbox{
        if( my $response = $dialog->Show() ) {
                        &quit;
                }else{
                        print "Der Dialog wurde nicht über einen der Buttons beantwortet.";
                }
        
}
sub quit{
        #SeaClear killen
        $readgps=0;
        $move=0;
        my $sc=qx(pidof $seaclear);
        if ($sc ne ""){qx(kill -9 $sc)}
        sleep(1);
        $sc=qx(pidof wineserver);
        if ($sc ne ""){qx(kill -9 $sc)}
        
        sleep(1);
        #Socat killen
        $sc=qx(pidof socat);
        if ($sc ne ""){qx(sudo kill -9 $sc)}
        if(qx(ls /dev/ttyUSB99 2>&1) eq '/dev/ttyUSB99'){
                system("/usr/bin/sudo /bin/rm ".$usbvsend." ".$usbvread);
        }
        $mw->destroy;
}