Ein einfacher Yacht-Navigator
Systemvoraussetzungen:
Hardware:
- Note- oder Netbook
- USB-GPS-Empfänger (empfohlen:USB-GPS-Empfänger VT-200)
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:
- 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;
}