Subversion Repositories Projects

Compare Revisions

Ignore whitespace Rev 549 → Rev 550

/MissionCockpit/tags/V0.2.5/libmkcockpit.pl
0,0 → 1,1611
#!/usr/bin/perl
#!/usr/bin/perl -d:ptkdb
 
###############################################################################
#
# libmkcockpit.pl - MK Mission Cockpit - Subroutined for GUI Frontend
#
# Copyright (C) 2009 Rainer Walther (rainerwalther-mail@web.de)
#
# Creative Commons Lizenz mit den Zusaetzen (by, nc, sa)
#
# Es ist Ihnen gestattet:
# * das Werk vervielfältigen, verbreiten und öffentlich zugänglich machen
# * Abwandlungen bzw. Bearbeitungen des Inhaltes anfertigen
#
# Zu den folgenden Bedingungen:
# * Namensnennung.
# Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
# * Keine kommerzielle Nutzung.
# Dieses Werk darf nicht für kommerzielle Zwecke verwendet werden.
# * Weitergabe unter gleichen Bedingungen.
# Wenn Sie den lizenzierten Inhalt bearbeiten oder in anderer Weise umgestalten,
# verändern oder als Grundlage für einen anderen Inhalt verwenden,
# dürfen Sie den neu entstandenen Inhalt nur unter Verwendung von Lizenzbedingungen
# weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind.
#
# Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen, unter welche dieses
# Werk fällt, mitteilen. Am Einfachsten ist es, einen Link auf diese Seite einzubinden.
#
# Jede der vorgenannten Bedingungen kann aufgehoben werden, sofern Sie die Einwilligung
# des Rechteinhabers dazu erhalten.
#
# Diese Lizenz lässt die Urheberpersönlichkeitsrechte unberührt.
#
# Weitere Details zur Lizenzbestimmung gibt es hier:
# Kurzform: http://creativecommons.org/licenses/by-nc-sa/3.0/de/
# Komplett: http://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode
#
###############################################################################
# 2009-08-09 0.2.5 rw subroutines moved from mkcockpit.pl
#
###############################################################################
 
$Version{'libmkcockpit.pl'} = "0.2.5 - 2009-08-09";
 
 
# check, if %MkOsd is valid
sub MkOsdIsValid()
{
return ( $MkOsd{'_Timestamp'} >= time-2 );
}
 
# check, if current GPS position is valid
sub CurPosIsValid()
{
return ( &MkOsdIsValid() and $MkOsd{'SatsInUse'} >= 6 and $MkOsd{'CurPos_Stat'} == 1 );
}
 
# check, if home GPS position is valid
sub HomePosIsValid()
{
return ( &MkOsdIsValid() and $MkOsd{'SatsInUse'} >= 6 and $MkOsd{'HomePos_Stat'} == 1 );
}
 
# check, if target GPS position is valid
sub TargetIsValid()
{
return ( &MkOsdIsValid() and $MkOsd{'SatsInUse'} >= 6 and $MkOsd{'TargetPos_Stat'} == 1 );
}
 
# check, if motor are on
sub MkIsMotorOn()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x01 );
}
 
# check, if MK is flying
sub MkIsFlying()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x02 );
}
 
# check, if MK is calibrating
sub MkIsCalibrating()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x04 );
}
# check, if Motor is starting
sub MkIsMotorStarting()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x08 );
}
 
# check, Emergency Landing
sub MkEmergencyLanding()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x10 );
}
 
# check, if MK is FREE Mode
sub MkIsFreeMode()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x01 );
}
 
# check, if MK is in PH Mode
sub MkIsPhMode()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x02 );
}
 
# check, if MK is in WPT Mode
sub MkIsWptMode()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x04 );
}
 
# check, Range Limit
sub MkRangeLimit()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x08 );
}
 
# check, Serial Link
sub MkSerialLink()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x10 );
}
 
# check, Target reached
sub MkTargetReached()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x20 );
}
 
# check, Manual Control
sub MkManualControl()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x40 );
}
 
 
# get battery capacity in %
sub BatCapacity()
{
my ($UBat) = @_;
 
my $CfgVal = $Cfg->{'mkcockpit'}->{'BatCharacteristics'};
my @Voltage = split ' ', $CfgVal;
 
my $Capacity = 0;
if ( $UBat >= $Voltage[0] )
{
$Capacity = 100;
}
 
$Cnt = $#Voltage;
for ($i=0; $i < $Cnt; $i++)
{
my $V1 = $Voltage[$i];
my $V2 = $Voltage[$i+1];
 
if ( $UBat >= $V1 and $UBat < $V2 or
$UBat <= $V1 and $UBat > $V2 )
{
# linear interpolation
my $x = $i + ($UBat - $V1 ) / ($V2 - $V1);
$Capacity = 100 - $x * 100 / $Cnt;
last;
}
}
 
return $Capacity;
}
 
 
#
# Waypoint handling
#
 
# Add a Waypoint to @Waypoints List
sub WpAdd()
{
my ($Wp_x, $Wp_y) = @_;
 
# save Wp-Hash in Waypoint-Array
my $Wp = {};
my $Tag = sprintf "Waypoint-%d.%d", time, int (rand(9)) ; # kind of unique Tag for this Wp
($Lat, $Lon) = &MapXY2Gps($Wp_x, $Wp_y);
$Wp->{'Tag'} = $Tag;
$Wp->{'MapX'} = $Wp_x;
$Wp->{'MapY'} = $Wp_y;
$Wp->{'Pos_Lat'} = $Lat;
$Wp->{'Pos_Lon'} = $Lon;
$Wp->{'Pos_Alt'} = $MkOsd{'CurPos_Alt'};
$Wp->{'Heading'} = $Cfg->{'waypoint'}->{'DefaultHeading'};
$Wp->{'ToleranceRadius'} = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
$Wp->{'Holdtime'} = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
$Wp->{'Event_Flag'} = $Cfg->{'waypoint'}->{'DefaultEventFlag'};
push @Waypoints, $Wp;
}
 
 
# Delete Waypoint from @Waypoints List
sub WpDelete ()
{
my ($WpIndex) = @_;
 
# delete Wp in Waypoint-Array
splice @Waypoints, $WpIndex, 1;
}
 
 
# Load @Waypoints from file
sub WpLoadFile ()
{
my ($WpFile) = @_;
 
# XML in Hash-Ref lesen
my $Wp = XMLin($WpFile, ForceArray => 1);
 
# XML Hash-Ref in Wp-Array umkopieren
undef @Waypoints;
 
foreach $key (sort keys %$Wp)
{
my $Point = $Wp->{$key}->[0];
 
# relative Pixelkoordinaten auf Bildgroesse umrechnen
if ( $Point->{'MapX'} <= 1 and $Point->{'MapY'} <= 1 )
{
$Point->{'MapX'} = int ( $Point->{'MapX'} * $MapSizeX + 0.5 );
$Point->{'MapY'} = int ( $Point->{'MapY'} * $MapSizeY + 0.5 );
}
 
# GPS Koordinaten für die aktuelle Karte neu aus Map x/y berechnen
my ($Lat, $Lon) = &MapXY2Gps($Point->{'MapX'}, $Point->{'MapY'});
$Point->{'Pos_Lat'} = $Lat;
$Point->{'Pos_Lon'} = $Lon;
push @Waypoints, $Point;
}
}
 
 
# Safe @Waypoints to file
sub WpSaveFile()
{
my ($WpFile) = @_;
 
# Waypoint-Array in Hash umkopieren
for $i ( 0 .. $#Waypoints )
{
my $key = sprintf ("WP-%04d", $i);
my $Wp = {%{$Waypoints[$i]}}; # copy of Hash-content
$WpOut{$key} = $Wp;
 
# Pixelkoordinaten relativ zur Bildgroesse speichern
$WpOut{$key}{'MapX_Pixel'} = $WpOut{$key}{'MapX'};
$WpOut{$key}{'MapY_Pixel'} = $WpOut{$key}{'MapY'};
$WpOut{$key}{'MapX'} /= $MapSizeX;
$WpOut{$key}{'MapY'} /= $MapSizeY;
}
 
# WP-Hash als XML speichern
&XMLout (\%WpOut,
'OutputFile' => $WpFile,
'AttrIndent' => '1',
'RootName' => 'Waypoints',
);
}
 
 
# Get Wp Index from Canvas Id
sub WpGetIndexFromId()
{
my ($id) = @_;
 
my @Tags = $map_canvas->gettags($id);
my $WpTag = $Tags[1];
 
for $i (0 .. $#Waypoints)
{
my $Wp = $Waypoints[$i];
if ( $Wp->{'Tag'} eq $WpTag )
{
# got it
return $i;
}
}
return -1;
}
 
# Resend all Waypoints to MK
sub WpSendAll()
{
# OSD/Debug Abfragefrequenz verringern, sonst kommen nicht alle Wp im MK an
# Sicherheitshalber doppelt senden
$MkSendWp = 1; # verhindert ueberschreiben im Timer
$MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) ); # Frequenz OSD Datensatz, * 10ms
$MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) ); # Frequenz MK Debug Datensatz, * 10ms
usleep (200000);
$MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) ); # Frequenz OSD Datensatz, * 10ms
$MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) ); # Frequenz MK Debug Datensatz, * 10ms
usleep (200000);
 
# Alte WP-Liste im MK löschen
my $Wp = $Waypoints[0];
&MkFlyTo ( '-lat' => $Wp->{'Pos_Lat'},
'-lon' => $Wp->{'Pos_Lon'},
'-mode' => "Waypoint Delete"
);
for $i (0 .. $#Waypoints)
{
my $Wp = $Waypoints[$i];
&MkFlyTo ( '-lat' => $Wp->{'Pos_Lat'},
'-lon' => $Wp->{'Pos_Lon'},
'-alt' => $Wp->{'Pos_Alt'},
'-heading' => $Wp->{'Heading'},
'-toleranceradius' => $Wp->{'ToleranceRadius'},
'-holdtime' => $Wp->{'Holdtime'},
'-eventflag' => $Wp->{'Event_Flag'},
'-mode' => "Waypoint"
);
usleep (150000) # NC Zeit zum Verarbeiten geben
}
 
$MkSendWp = 0; # normale OSD/Debug Abfragefrequenz wird automatisch im 5s Timer wieder eingestellt
 
# gray connectors: Wp are sent to MK
$map_canvas->itemconfigure('Waypoint-Connector',
'-fill' => $Cfg->{'mkcockpit'}->{'ColorWpConnector'},
);
 
# MK ist nun synchron mit @Waypoints
$WaypointsModified = 0;
}
 
# Redraw Waypoint Icons
sub WpRedrawIcons()
{
if ( $PlayerWptKmlMode =~ /WPT/i )
{
 
# delete old icons and Wp-Number from canvas
$map_canvas->delete('Waypoint');
$map_canvas->delete('WaypointNumber');
 
# create new icons
for $i (0 .. $#Waypoints)
{
my $Wp = $Waypoints[$i];
my $x = $Wp->{'MapX'};
my $y = $Wp->{'MapY'};
my $Tag = $Wp->{'Tag'};
 
# Waypoint Icon
my $IconHeight = 48;
my $IconWidth = 48;
$map_canvas->createImage($x-$IconWidth/2, $y-$IconHeight,
'-tags' => ['Waypoint', $Tag],
'-anchor' => 'nw',
'-image' => 'Waypoint-Photo',
);
# Waypoint Number
my $WpNumber = $i + 1;
$map_canvas->createText ( $x+3, $y-$IconHeight/2+12,
'-tags' => ['WaypointNumber', $Tag],
'-text' => $WpNumber,
'-font' => '-*-Arial-Bold-R-Normal--*-100-*',
'-fill' => $Cfg->{'mkcockpit'}->{'ColorWpNumber'},
'-anchor' => 'w',
);
 
}
$map_canvas->lower('Waypoint', 'Fox'); # waypoint below Fox
$map_canvas->lower('WaypointNumber', 'Waypoint'); # waypoint-number below waypoint
}
}
 
# Redraw Waypoint connectors
sub WpRedrawLines()
{
if ( $PlayerWptKmlMode =~ /WPT/i )
{
# delete old connectors from canvas
$map_canvas->delete('Waypoint-Connector');
 
my $Color = $Cfg->{'mkcockpit'}->{'ColorWpConnector'};
if ( $WaypointsModified )
{
$Color = $Cfg->{'mkcockpit'}->{'ColorWpResend'};
}
 
my $Wp = $Waypoints[0];
my $x_last = $Wp->{'MapX'};
my $y_last = $Wp->{'MapY'};
for $i (1 .. $#Waypoints)
{
my $Wp = $Waypoints[$i];
my $x = $Wp->{'MapX'};
my $y = $Wp->{'MapY'};
 
$map_canvas->createLine ( $x_last, $y_last, $x, $y,
'-tags' => 'Waypoint-Connector',
'-arrow' => 'last',
'-arrowshape' => [10, 10, 3 ],
'-fill' => $Color,
'-width' => 1,
);
$x_last = $x;
$y_last = $y;
}
$map_canvas->lower('Waypoint-Connector', 'Waypoint'); # connector below waypoint
}
}
 
 
# Hide Waypoints and connectors on Canvas
sub WpHide()
{
$map_canvas->delete('Waypoint');
$map_canvas->delete('WaypointNumber');
$map_canvas->delete('Waypoint-Connector');
}
 
# Hide Kml-Track on Canvas
sub KmlHide()
{
$map_canvas->delete('KML-Track');
}
 
 
# Load @KmlTargets from file
sub KmlLoadFile()
{
my ($File) = @_;
 
# XML in Hash-Ref lesen
my $Kml = XMLin($File);
 
# init state maschine
undef @KmlTargets;
$KmlPlayerIndex = 0;
 
my $Coordinates = $Kml->{Document}->{Placemark}->{LineString}->{coordinates};
foreach $Line (split "\n", $Coordinates)
{
chomp $Line;
$Line =~ s/\s//g; # remove white space
if ( $Line ne "" )
{
my ($Lon, $Lat, $Alt) = split ",", $Line;
$Lon = sprintf ("%f", $Lon);
$Lat = sprintf ("%f", $Lat);
$Alt = sprintf ("%f", $Alt);
 
push @KmlTargets, {'Lat' => $Lat,
'Lon' => $Lon,
'Alt' => $Alt,
};
}
}
}
 
 
 
 
# Redraw KML track
sub KmlRedraw()
{
 
# delete old Track from canvas
$map_canvas->delete('KML-Track');
 
my @Track;
 
foreach $Target ( @KmlTargets )
{
my $Lat = $Target->{'Lat'};
my $Lon = $Target->{'Lon'};
my $Alt = $Target->{'Alt'};
my ($x, $y) = &MapGps2XY($Lat, $Lon);
push @Track, $x, $y;
}
 
if ( scalar @Track >= 4 ) # at least 2 Koordinaten-Paare
{
$map_canvas->createLine ( @Track,
'-tags' => 'KML-Track',
'-fill' => $Cfg->{'mkcockpit'}->{'ColorKmlTrack'},
'-width' => 1,
);
 
$map_canvas->lower('KML-Track', 'Target'); # Track below Target
}
}
 
 
# Redraw Footprint
sub FootprintRedraw()
{
# delete old Footprint from canvas
$map_canvas->delete('Footprint');
 
if ( scalar @Footprint >= 4 ) # at least 2 Koordinaten-Paare
{
$map_canvas->createLine ( @Footprint,
'-tags' => 'Footprint',
'-fill' => $Cfg->{'mkcockpit'}->{'ColorFootprint'},
'-width' => 1,
);
}
$map_canvas->lower('Footprint', 'Fox');
}
 
 
# Waypoint Player: Set Waypoint - sequence or random
sub WpTargetSet()
{
my ($Index) = @_;
 
my $WpCnt = scalar @Waypoints;
if ( $Index < 0 or $Index >= $WpCnt )
{
# invalid WP number
return 1;
}
 
my $Wp = $Waypoints[$Index];
my $Wp_x = $Wp->{'MapX'};
my $Wp_y = $Wp->{'MapY'};
 
# is Wp reachable?
if ( ! &IsTargetReachable($Wp_x, $Wp_y) )
{
# new Wp-Target is not reachable
return 1;
}
 
# set new Wp-Target
$WpPlayerIndex = $Index;
$WpPlayerHoldtime = -1;
 
return 0;
}
 
 
# Waypoint Player: Goto next Waypoint - sequence or random
sub WpTargetNext()
{
my ($ParIndex) = @_;
 
my $WpCnt = scalar @Waypoints;
 
# Std- or Random Waypoint sequence
if ( $PlayerRandomMode =~ /STD/i or
$PlayerRandomMode =~ /RND/i )
{
$NewIndex = $WpPlayerIndex;
 
# get next Wp
for ( $i=0; $i<5; $i++) # avoid deadlock, if no WP reachable
{
for ( $j=0; $j<5; $j++ ) # avoid deadlock, if only 1 WP
{
if ( $PlayerRandomMode =~ /STD/i )
{
$NewIndex ++;
if ( $NewIndex >= $WpCnt )
{
# Restart with 1st Wp
$NewIndex = 0;
}
}
if ( $PlayerRandomMode =~ /RND/i )
{
$NewIndex = int (rand($WpCnt));
}
 
# want to have different Wp
if ( $NewIndex ne $WpPlayerIndex )
{
last;
}
}
 
# Set new Target
if ( &WpTargetSet ($NewIndex) == 0 )
{
# new Wp-Target set
last;
}
}
}
 
# Random Map sequence
if ( $PlayerRandomMode =~ /MAP/i )
{
$RandomTarget_x = $MkPos_x;
$RandomTarget_y = $MkPos_y;
 
for ( $i=0; $i<50; $i++) # avoid deadlock, if target not reachable
{
# don't use 10% around the map
my $New_x = int (rand($MapSizeX - 2 * $MapSizeX/10));
my $New_y = int (rand($MapSizeY - 2 * $MapSizeY/10));
$New_x += $MapSizeX/10;
$New_y += $MapSizeY/10;
 
# is Target reachable?
if ( &IsTargetReachable($New_x, $New_y) )
{
# new Target found
$RandomTarget_x = $New_x;
$RandomTarget_y = $New_y;
last;
}
}
}
&TtsSpeak ('MEDIUM', $Translate{'TtsNextTarget'});
 
$WpPlayerHoldtime = -1;
}
 
 
# Waypoint Player: Goto previous Waypoint
sub WpTargetPrev()
{
if ( $PlayerRandomMode =~ /STD/i )
{
$WpPlayerIndex --;
if ( $WpPlayerIndex < 0 )
{
# Restart with last Wp
$WpPlayerIndex = $#Waypoints;
}
}
else
{
# Next Random Target
&WpTargetNext();
}
 
$WpPlayerHoldtime = -1;
}
 
 
# Waypoint Player: Goto first Waypoint
sub WpTargetFirst()
{
$WpPlayerIndex = 0;
$WpPlayerHoldtime = -1;
}
 
# Waypoint Player: Goto last Waypoint
sub WpTargetLast()
{
$WpPlayerIndex = $#Waypoints;
$WpPlayerHoldtime = -1;
}
 
 
# Waypoint Player: Waypoint Target reached?
sub WpCheckTargetReached()
{
if ( $WpPlayerHoldtime == -1 )
{
lock (%MkOsd); # until end of block
 
if ( &CurPosIsValid() and &HomePosIsValid() and &MkIsWptMode() )
{
# Gueltige SAT Daten
 
# for Wp mode
my $Wp = $Waypoints[$WpPlayerIndex];
my $WpTarget_Lat = $Wp->{'Pos_Lat'};
my $WpTarget_Lon = $Wp->{'Pos_Lon'};
my $WpTolerance = $Wp->{'ToleranceRadius'};
my $WpHoldtime = $Wp->{'Holdtime'};
 
# Random-Map Mode
if ( $PlayerRandomMode =~ /MAP/i )
{
($WpTarget_Lat, $WpTarget_Lon) = &MapXY2Gps ($RandomTarget_x, $RandomTarget_y);
$WpTolerance = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
$WpHoldtime = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
}
 
# Operation Radius pruefen
my ($HomeDist, $HomeBearing) = &MapGpsTo($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
if ( $HomeDist > $MkOsd{'OperatingRadius'} )
{
# Target entsprechend Operation Radius neu berechnen
$HomeDist = $MkOsd{'OperatingRadius'};
($WpTarget_Lat, $WpTarget_Lon) = &MapGpsAt($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $HomeDist, $HomeBearing);
}
 
# Abstand zum Ziel pruefen
my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
$Dist = int ($Dist + 0.5);
if ( $Dist <= $WpTolerance )
{
# Target reached - count down Holdtime
$WpPlayerHoldtime = 2 * $WpHoldtime; # 0..2n - decrement im 0.5s timer
 
&TtsSpeak ('MEDIUM', $Translate{'TtsTargetReached'});
}
}
}
 
if ( $WpPlayerHoldtime == 0 ) # wird im 0.5s timer runtergezaehlt
{
# Target reached - Holdtime is over
$WpPlayerHoldtime = -1;
 
return 1;
}
 
# Target NOT reached
return 0;
}
 
 
# KML Player: 10s forward
sub KmlTargetNext()
{
$KmlPlayerIndex += int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
if ( $KmlPlayerIndex > $#KmlTargets )
{
# Next loop
$KmlPlayerIndex -= $#KmlTargets;
}
}
 
# KML Player: 10s backward
sub KmlTargetPrev()
{
$KmlPlayerIndex -= int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
if ( $KmlPlayerIndex < 0 )
{
# Next loop
$KmlPlayerIndex += $#KmlTargets;
}
}
 
# KML Player: Goto first Target
sub KmlTargetFirst()
{
$KmlPlayerIndex = 0;
}
 
# KML Player: Goto last Target
sub KmlTargetLast()
{
$KmlPlayerIndex = $#KmlTargets;
}
 
 
#
# Set Player modes
#
 
# set player to "Play" mode
sub PlayerPlay()
{
$PlayerMode = 'Play';
$WpPlayerHoldtime = -1;
 
# Play/Pause-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-PlayPause');
$map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
'-tags' => 'Wp-PlayPause',
'-anchor' => 'nw',
'-image' => 'WpPause-Foto',
);
}
 
 
# set player to "Pause" mode
sub PlayerPause()
{
$PlayerMode = 'Pause';
$WpPlayerHoldtime = -1;
 
# Play/Pause-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-PlayPause');
$map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
'-tags' => 'Wp-PlayPause',
'-anchor' => 'nw',
'-image' => 'WpPlay-Foto',
);
 
# momentane Position merken und im Player-Timer senden
$PlayerPause_Lon = "";
$PlayerPause_Lat = "";
 
lock (%MkOsd); # until end of block
if ( &MkOsdIsValid() )
{
# Gueltige OSD Daten
if ( $MkOsd{'SatsInUse'} >= 6 and $MkOsd{'CurPos_Stat'} == 1 )
{
$PlayerPause_Lon = $MkOsd{'CurPos_Lon'};
$PlayerPause_Lat = $MkOsd{'CurPos_Lat'};
}
}
}
 
 
# set player to "Home" mode
sub PlayerHome()
{
 
$PlayerMode = 'Home';
&WpTargetFirst();
 
# Play/Pause-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-PlayPause');
$map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
'-tags' => 'Wp-PlayPause',
'-anchor' => 'nw',
'-image' => 'WpPlay-Foto',
);
}
 
 
# set player to "Stop" mode
sub PlayerStop()
{
$PlayerMode = 'Stop';
&WpTargetFirst();
 
# set Play/Pause Icon to "Play
$map_canvas->delete('Wp-PlayPause');
$map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
'-tags' => 'Wp-PlayPause',
'-anchor' => 'nw',
'-image' => 'WpPlay-Foto',
);
 
# switch player to Wp Mode
&PlayerWpt();
}
 
 
# set player Random Mode to "STD"
sub PlayerRandomStd()
{
$PlayerRandomMode = "STD";
 
# Set Icon
$map_canvas->delete('Wp-WptRandom');
$map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
'-tags' => 'Wp-WptRandom',
'-anchor' => 'nw',
'-image' => 'WpRandomOn-Foto',
);
 
# redraw connectors and Icons on canvas
&WpRedrawLines();
&WpRedrawIcons();
}
 
 
# set player Random Mode to "RND"
sub PlayerRandomRnd()
{
$PlayerRandomMode = "RND";
 
# Set Icon
$map_canvas->delete('Wp-WptRandom');
$map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
'-tags' => 'Wp-WptRandom',
'-anchor' => 'nw',
'-image' => 'WpRandomMap-Foto',
);
 
# delete Wp-connectors from canvas
$map_canvas->delete('Waypoint-Connector');
}
 
 
# set player Random Mode to "MAP"
sub PlayerRandomMap()
{
$PlayerRandomMode = "MAP";
 
# Set Icon
$map_canvas->delete('Wp-WptRandom');
$map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
'-tags' => 'Wp-WptRandom',
'-anchor' => 'nw',
'-image' => 'WpRandomOff-Foto',
);
 
# Get 1st Target
&WpTargetNext();
 
# hide WP and connectors on canvas
&WpHide();
}
 
 
# set player to KML mode
sub PlayerKml()
{
$PlayerWptKmlMode = 'KML';
 
# Wpt/Kml-Player-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-WptKml');
$map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
'-tags' => 'Wp-WptKml',
'-anchor' => 'nw',
'-image' => 'WpKml-Foto',
);
 
# delete Waypoints from canvas
&WpHide();
 
# show KML Track
&KmlRedraw();
}
 
 
# set player to WPT mode
sub PlayerWpt()
{
$PlayerWptKmlMode = 'WPT';
 
# Wpt/Kml-Player-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-WptKml');
$map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
'-tags' => 'Wp-WptKml',
'-anchor' => 'nw',
'-image' => 'WpWpt-Foto',
);
 
# delete Kml-Track from canvas
&KmlHide();
 
# Show waypoints, WP resend required
$WaypointsModified = 1;
 
if ( $PlayerRandomMode ne 'MAP' )
{
&WpRedrawIcons()
}
if ( $PlayerRandomMode eq 'STD' )
{
&WpRedrawLines()
}
 
}
 
 
# Activate Recording mode
sub PlayerRecordOn
{
$PlayerRecordMode = "REC";
$map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "Recording" );
 
# Record new KML-Track
undef @KmlTargets;
$KmlPlayerIndex = 0;
 
# delete Kml-Track from canvas
&KmlHide();
}
 
# Deactivate Recording mode
sub PlayerRecordOff
{
$PlayerRecordMode = "";
$map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "" );
}
 
 
#
# System Messages
#
 
# Init Messages for a Subsystem/timer
sub MkMessageInit ()
{
my ($Id) = @_;
 
$MkMessages{$Id} = [];
}
 
 
# Register message
sub MkMessage ()
{
my ($Message, $Id) = @_;
 
push @{$MkMessages{$Id}}, $Message;
}
 
 
# show registered messages
sub MkMessageShow()
{
my @Messages;
my $MsgLines = 0;
my $MaxMsgLen = 0;
 
# Collect Messages of each category
foreach my $Id (keys %MkMessages)
{
foreach $i ( 0 .. $#{$MkMessages{$Id}} )
{
my $Msg = $MkMessages{$Id}[$i];
push @Messages, $Msg;
 
$MsgLines ++;
my $Len = length $Msg;
if ( $Len > $MaxMsgLen )
{
$MaxMsgLen = $Len;
}
}
}
 
$map_canvas->delete('Message-Balloon'); # delete old Balloon
 
if ( $MsgLines > 0 )
{
# draw Balloon
my @MsgBalloon = ( $MkPos_x , $MkPos_y,
$MkPos_x + 30 , $MkPos_y + 40,
$MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 40,
$MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 44 + $MsgLines * 20,
$MkPos_x + 20, $MkPos_y + 44 + $MsgLines * 20,
$MkPos_x + 20, $MkPos_y + 40,
$MkPos_x, $MkPos_y,
);
 
$map_canvas->createPolygon( @MsgBalloon,
'-tags' => ['Message-Balloon', 'Message-BalloonBubble'],
'-fill' => 'yellow',
'-outline' => 'yellow',
'-width' => 1,
);
# draw Messages
my $MsgLine = 1;
foreach my $Msg (@Messages)
{
$map_canvas->createText ( $MkPos_x + 25, $MkPos_y + 32 + $MsgLine * 20 ,
'-tags' => ['Message-Balloon', 'Message-BalloonText'],
'-text' => $Msg,
'-font' => '-*-Arial-Bold-R-Normal--*-200-*',
'-fill' => 'blue',
'-anchor' => 'w',
);
$MsgLine ++;
}
 
 
$map_canvas->lower('Message-Balloon', 'MK-Arrow');
}
 
}
 
 
# Show Balloon, when arproaching Target
sub TargetMessageShow ()
{
$map_canvas->delete('Target-Balloon'); # delete old Balloon
 
if ( $OperationMode ne "Free" and $MkOsd{'TargetPos_Stat'} == 1 and $MkOsd{'TargetPosDev_Dist'} /10 < 25 )
{
my $BalloonLines = 0;
$ColorBalloon = "blue";
my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
my $Wp = $Waypoints[$MkOsd{'WaypointIndex'}];
 
# Holdtime Wp-Player Mode
if ( $WpPlayerHoldtime >= 0 )
{
# Holdtime
$ColorBalloon = 'red';
my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($WpPlayerHoldtime / 2 + 0.5) );
$map_canvas->createText ( $T_x + 25, $T_y - 40,
'-tags' => ['Target-Balloon', 'Target-BalloonText'],
'-text' => $HoldTime,
'-font' => '-*-Arial-Bold-R-Normal--*-200-*',
'-fill' => $ColorBalloon,
'-anchor' => 'w',
);
$BalloonLines ++;
}
 
# Holdtime WPT-Mode
if ( &MkTargetReached() and $OperationMode eq "WPT" )
{
# Holdtime from MK
$ColorBalloon = 'red';
my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($MkOsd{'TargetHoldTime'} + 0.5) );
$map_canvas->createText ( $T_x + 25, $T_y - 40,
'-tags' => ['Target-Balloon', 'Target-BalloonText'],
'-text' => $HoldTime,
'-font' => '-*-Arial-Bold-R-Normal--*-200-*',
'-fill' => $ColorBalloon,
'-anchor' => 'w',
);
$BalloonLines ++;
}
 
# Tolerance Radius Player Mode
if ( &MkIsWptMode() and $OperationMode eq "Play" and $PlayerWptKmlMode eq "WPT" )
{
my $WpTolerance = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'});
$map_canvas->createText ( $T_x + 25, $T_y - 60,
'-tags' => ['Target-Balloon', 'Target-BalloonText'],
'-text' => $WpTolerance,
'-font' => '-*-Arial-Bold-R-Normal--*-200-*',
'-fill' => $ColorBalloon,
'-anchor' => 'w',
);
$BalloonLines ++;
}
 
# Tolerance WPT-Mode
if ( &MkIsWptMode and $OperationMode eq "WPT" )
{
my $WpTolerance = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'} );
$map_canvas->createText ( $T_x + 25, $T_y - 60,
'-tags' => ['Target-Balloon', 'Target-BalloonText'],
'-text' => $WpTolerance,
'-font' => '-*-Arial-Bold-R-Normal--*-200-*',
'-fill' => $ColorBalloon,
'-anchor' => 'w',
);
$BalloonLines ++;
}
 
# Distance to Target
my $Dist = int ($MkOsd{'TargetPosDev_Dist'} /10 + 0.5);
$map_canvas->createText ( $T_x + 25, $T_y - 80,
'-tags' => ['Target-Balloon', 'Target-BalloonText'],
'-text' => sprintf ("%5s %3d m", "DST:", $Dist) ,
'-font' => '-*-Arial-Bold-R-Normal--*-200-*',
'-fill' => $ColorBalloon,
'-anchor' => 'w',
);
$BalloonLines ++;
 
if ( $BalloonLines >= 1 )
{
# draw Balloon
my @TargetBalloon = ( $T_x , $T_y,
$T_x + 30, $T_y - (3 - $BalloonLines) * 20 -27,
$T_x + 150, $T_y - (3 - $BalloonLines) * 20 -27 ,
$T_x + 150, $T_y - 93,
$T_x + 20, $T_y - 93,
$T_x + 20, $T_y - (3 - $BalloonLines) * 20 -27,
$T_x, $T_y,
);
 
$map_canvas->createPolygon( @TargetBalloon,
'-tags' => ['Target-Balloon', 'Target-BalloonBubble'],
'-fill' => 'lightgray',
'-outline' => 'yellow',
'-width' => 1,
);
}
 
 
$map_canvas->lower('Target-Balloon', 'MK-Home-Line');
$map_canvas->lower('Target-BalloonBubble', 'Target-BalloonText');
}
}
 
 
#
# Airfield border
#
 
# Are two segments A(a1/a2), B(b1/b2) and C(c1/c2), D(d1/d2) crossing ?
sub SegmentCross()
{
my ( $a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) = @_;
 
# segment C/D ist vertical, avoid div/0
if ( $c1 == $d1 )
{
$d1 += 0.00001;
}
 
my $n = ($b1 - $a1) * ($d2 - $c2) - ($b2 - $a2) * ($d1 - $c1);
if ( $n == 0.0 )
{
# AB und CD sind parallel
return 0;
}
 
my $s = ( ($c1 - $a1) * ($d2 - $c2) - ($c2 - $a2) * ($d1 - $c1) ) / $n;
my $t = ( $a1 - $c1 + $s * ($b1 - $a1) ) / ( $d1 - $c1 );
if ( $s >= 0.0 and $s <= 1.0 and $t >= 0.0 and $t <= 1.0 )
{
# beide Strecken kreuzen sich
 
# Schnittpunkt: s_x, s_y
my $s_x = $a1 + $s * ( $b1 - $a1 );
my $s_y = $a2 + $s * ( $b2 - $a2 );
 
return 1;
}
 
# beide Strecken kreuzen sich nicht
return 0;
}
 
 
# How often does a segment A(a1,a2), B(b1,b2) cross the polygon?
sub SegmentPolygonCross()
{
my ( $a1, $a2, $b1, $b2, $Polygon) = @_;
 
my $Cross = 0;
my $PolyCnt = scalar @{$Polygon};
my $PolyPointCnt = $PolyCnt / 2;
 
my $i = 0;
for ( $p=0; $p < $PolyPointCnt; $p++ )
{
my $c1 = ${$Polygon}[$i++];
my $c2 = ${$Polygon}[$i++];
 
if ( $i >= $PolyCnt ) { $i = 0; }
 
my $d1 = ${$Polygon}[$i];
my $d2 = ${$Polygon}[$i+1];
 
# map calibration offsets
$c1 -= $Map{'Offset_x'};
$c2 += $Map{'Offset_y'};
$d1 -= $Map{'Offset_x'};
$d2 += $Map{'Offset_y'};
 
if ( &SegmentCross($a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) )
{
$Cross ++;
}
}
 
return $Cross;
}
 
 
# Is point A inside airfield border?
sub IsInsideBorder()
{
my ($a1, $a2) = @_;
 
if ( scalar @Map{'Border'} == 0 )
{
# no border defined, always inside
return 1;
}
 
my $Cross = &SegmentPolygonCross (-10, -10, $a1, $a2, @Map{'Border'} );
 
# Ungerade Anzahl Kreuzungen: Inside
return ( $Cross % 2 );
}
 
 
 
# Is segment A, B crossing the airfield border?
sub IsCrossingBorder()
{
my ($a1, $a2, $b1, $b2) = @_;
 
if ( scalar @Map{'Border'} == 0 )
{
# no border defined, always not crossing
return 0;
}
 
my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
 
return ( $Cross > 0 );
}
 
 
# How often is segment A, B crossing the airfield border?
sub CrossingBorderCount()
{
my ($a1, $a2, $b1, $b2) = @_;
 
if ( scalar @Map{'Border'} == 0 )
{
# no border defined, not crossing
return 0;
}
 
my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
 
return ( $Cross );
}
 
 
# check, if Target is reachable my MK
sub IsTargetReachable()
{
my ($T_x, $T_y) = @_;
 
my $MkIsInside = &IsInsideBorder($MkPos_x, $MkPos_y);
my $TargetIsInside = &IsInsideBorder($T_x, $T_y);
my $MkTargetCrossingCount = &CrossingBorderCount($MkPos_x, $MkPos_y, $T_x, $T_y);
 
if ( ($MkIsInside and $MkTargetCrossingCount == 0 ) or
(! $MkIsInside and $TargetIsInside and $MkTargetCrossingCount == 1) )
{
# Target is reachable
return 1;
}
 
# Target is not reachable
return 0;
}
 
 
 
#
# Configuration and data-visualisation
#
 
# Display or Modify Hash
sub DisplayHash()
{
my ($hrefData, $Titel, $Mode) = @_;
 
# $Mode: Display, Edit, Waypoint, Refresh
 
my %Id;
my $Label;
my $Value;
 
# Neues Fenster aufmachen
my $popup = $main->Toplevel();
$popup->title($Titel);
# Buttons
my $popup_button = $popup->Frame() -> pack('-side' => 'bottom',
'-expand' => 'y',
'-anchor' => 's',
'-padx' => 5,
'-pady' => 5,
);
$popup_button->Button('-text' => 'Schließen',
'-command' => sub
{
if ( $Mode =~ /edit/i and $Mode =~ /waypoint/i )
{
$WaypointsModified = 1;
&WpRedrawLines();
&WpRedrawIcons();
}
 
$popup->destroy()
})->pack;
 
# Frame mit den Labels
my $popup_label = $popup->Frame() -> pack('-side' => 'left',
'-expand' => 'y',
'-anchor' => 'w',
'-padx' => 10,
'-pady' => 10,
);
# Labels anzeigen
foreach $Label ( sort keys %{$hrefData})
{
if ( $Translate{$Label} ne "" )
{
$Label = $Translate{$Label};
}
$popup_label->Label ('-text' => $Label,
'-width' => 25,
'-anchor' => 'w',
) -> pack();
}
# Frame mit den Daten
my $popup_values = $popup->Frame() -> pack('-side' => 'left',
'-expand' => 'y',
'-anchor' => 'w',
'-padx' => 10,
'-pady' => 10,
);
# Daten anzeigen
foreach $Value ( sort keys %{$hrefData})
{
if ( $Mode =~ /display/i )
{
# Display
$Id{$Value} = $popup_values->Label ('-text' => ${$hrefData}{$Value},
'-width' => 20,
'-anchor' => 'e',
'-relief' => 'sunken',
) -> pack();
}
if ( $Mode =~ /edit/i )
{
# Edit
$Id{$Value} = $popup_values->Entry ('-textvariable' => \${$hrefData}{$Value},
'-exportselection' => '1',
'-width' => 20,
'-relief' => 'sunken',
) -> pack();
if ( $Mode =~ /waypoint/i )
{
# einige Waypoint-Felder nicht aenderbar einstellen
if ( "MapX MapY Pos_Lat Pos_Lon Tag" =~ /$Value/i )
{
$Id{$Value}->configure('-state' => 'disabled', );
}
}
}
}
 
if ( $Mode =~ /refresh/i )
{
# Timer: 0.1s
$popup_values->repeat (100, sub
{
# Datenfelder alle 100ms aktualisieren
 
my $BgColor = 'white';
if ( $Mode =~ /heartbeat/i )
{
$BgColor = 'red';
if ( &MkOsdIsValid() )
{
# gültige daten vom MK
$BgColor = 'white';
}
}
foreach $Value ( sort keys %{$hrefData} )
{
# Eingebbare Waypoint-Felder nicht aktualisieren
if ( ! ($Mode =~ /waypoint/i and
"Event_Flag Heading ToleranceRadius HoldTime Pos_Alt" =~ /$Value/i) )
{
$Id{$Value}->configure('-text' => ${$hrefData}{$Value},
'-background' => "$BgColor",
);
}
}
});
}
 
return 0;
}
 
 
 
# Konfigurationsdatei mkcockpit.xml im Popup-Fenster editieren
sub Configure()
{
 
# Copy Cfg-Hash for editing
my $CfgEdit = {%{$Cfg}};
foreach $key (keys %{$Cfg})
{
if ( ref $Cfg->{$key} )
{
$CfgEdit->{$key} = {%{$Cfg->{$key}}};
}
}
 
# Neues Fenster aufmachen
my $popup = $main->Toplevel();
$popup->title("Einstellungen - $XmlConfigFile");
 
# jede Sektion in einem Tab anzeigen
my $book = $popup->NoteBook()->pack( -fill=>'both', -expand=>1 );
foreach $key (sort keys %{$CfgEdit})
{
if ( ! ref $CfgEdit->{$key} )
{
next;
}
 
my $TabLabel = "$key";
if ( $Translate{$key} ne "" )
{
$TabLabel = $Translate{$key};
}
 
my $Tab = $book->add( "$key", -label=>"$TabLabel", );
 
# Frame fuer Buttons
my $book_button = $Tab->Frame() -> pack('-side' => 'bottom',
'-expand' => 'y',
'-anchor' => 's',
'-padx' => 5,
'-pady' => 5,
);
 
$book_button->Button('-text' => 'OK',
'-width' => '10',
'-command' => sub
{
# Copy back CfgEdit-Hash
$Cfg = {%{$CfgEdit}};
foreach $key (keys %{$CfgEdit})
{
if ( ref $CfgEdit->{$key} )
{
$Cfg->{$key} = {%{$CfgEdit->{$key}}};
}
}
 
# set new timestamp
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
my $TimeStamp = sprintf ("%04d%02d%02d-%02d%02d%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
$Cfg->{'CreationDate'} = $TimeStamp;
 
# Cfg in mkcockpit.xml speichern
&XMLout ($Cfg,
'OutputFile' => $XmlConfigFile,
'AttrIndent' => '1',
'RootName' => 'mkcockpit-Config',
);
 
$popup->destroy();
} )->pack ('-side' => 'left',
'-expand' => 'y',
'-anchor' => 's',
'-padx' => 5,
'-pady' => 5,
);
$book_button->Button('-text' => $Translate{'Abort'},
'-width' => '10',
'-command' => sub { $popup->destroy() },
)->pack ('-side' => 'left',
'-expand' => 'y',
'-anchor' => 's',
'-padx' => 5,
'-pady' => 5,
);
$book_button->Label ('-text' => $Translate{'RestartRequired'},
'-anchor' => 'w',
'-foreground' => 'red',
) ->pack ('-side' => 'left',
'-expand' => 'y',
'-anchor' => 's',
'-padx' => 10,
'-pady' => 5,
);
 
# Frame mit den Labels
my $popup_label = $Tab->Frame() -> pack('-side' => 'left',
'-expand' => 'y',
'-anchor' => 'w',
'-padx' => 10,
'-pady' => 10,
);
# Labels anzeigen
foreach $Label ( sort keys %{$CfgEdit->{$key}})
{
if ( $Translate{$Label} ne "" )
{
$Label = $Translate{$Label};
}
$popup_label->Label ('-text' => $Label,
'-width' => 35,
'-anchor' => 'w',
) -> pack();
}
# Frame mit den Daten
my $popup_values = $Tab->Frame() -> pack('-side' => 'left',
'-expand' => 'y',
'-anchor' => 'w',
'-padx' => 10,
'-pady' => 10,
);
# Eingabefelder mit Daten anzeigen
foreach $Value ( sort keys %{$CfgEdit->{$key}})
{
$popup_values->Entry ('-textvariable' => \$CfgEdit->{$key}->{$Value},
'-exportselection' => '1',
'-width' => 30,
'-relief' => 'sunken',
) -> pack();
}
}
}
 
 
1;
 
__END__