Subversion Repositories Projects

Compare Revisions

Ignore whitespace Rev 732 → Rev 733

/MissionCockpit/tags/V0.4.0/MissionCockpit-V0.4.0.zip
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream
Property changes:
Added: svn:mime-type
+application/octet-stream
\ No newline at end of property
/MissionCockpit/tags/V0.4.0/libmktimer.pl
0,0 → 1,1607
#!/usr/bin/perl
#!/usr/bin/perl -d:ptkdb
 
###############################################################################
#
# libmktime.pl - MK Mission Cockpit - Timer 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 Timer moved from mkcockpit.pl
# Optional Player home-pos in map configuration
# 2009-08-23 0.2.6 rw Tracking-Antenna Icon
# POI heading control
# 2009-10-11 0.2.7 rw Tracker control changed
# 2010-02-10 0.4.0 rw Altitude average changed
# Event Engine timer
# Serial channel timer
# External Control rimer
# Crosshair Timer
# 5s timer changed to 3s
# Current, UsedCapacity, Power added
# 2010-02-13 0.4.1 rw bugfix JoystickButton min-value
#
###############################################################################
 
$Version{'libmktimer.pl'} = "0.4.1 - 2010-02-13";
 
use Math::Trig;
use Time::HiRes qw(gettimeofday); # http://search.cpan.org/~jhi/Time-HiRes-1.9719/HiRes.pm
 
#
# Timer: 3s
#
$main->repeat (3000, sub
{
if ( ! $MkSendWp )
{
# Abfragefrequenz OSD und Debug regelmäßig neu einstellen, falls Übertragungsfehler
$MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 10) ); # Frequenz OSD Datensatz, * 10ms
$MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 10) ); # Frequenz MK Debug Datensatz, * 10ms
$MkSendQueue->enqueue( "v", "$AddrNC", ""); # Version
$MkSendQueue->enqueue( "e", "$AddrNC", ""); # Error Text Request
}
 
lock (%MkOsd); # until end of block
 
# Draw Operation Radius Border
$map_canvas->delete('Map-Border-OperatingRadius');
if ( &HomePosIsValid() )
{
my $Radius = $MkOsd{'OperatingRadius'};
my $H_Lat = $MkOsd{'HomePos_Lat'};
my $H_Lon = $MkOsd{'HomePos_Lon'};
my $Angel = &MapAngel();
 
my ($T_Lat, $T_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel -90);
my ($R_Lat, $R_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel);
my ($B_Lat, $B_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel + 90);
my ($L_Lat, $L_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel + 180);
 
my ($T_x, $T_y) = &MapGps2XY ($T_Lat, $T_Lon);
my ($R_x, $R_y) = &MapGps2XY ($R_Lat, $R_Lon);
my ($B_x, $B_y) = &MapGps2XY ($B_Lat, $B_Lon);
my ($L_x, $L_y) = &MapGps2XY ($L_Lat, $L_Lon);
 
$map_canvas->createArc ( $L_x, $B_y, $R_x, $T_y,
'-tags' => 'Map-Border-OperatingRadius',
'-extent' => '359',
'-start' => '0',
'-style' => 'chord',
'-outline' => $Cfg->{'mkcockpit'}->{'ColorAirfield'},
'-width' => '1',
);
$map_canvas->raise('Map-Border-OperatingRadius', 'Map'); # Border above Map
}
 
});
 
 
 
#
# Timer: 0.1s - Map Overlay aktualisieren
#
$frame_map_top->repeat (100, sub
{
 
# Clear old messages from this timer
&MkMessageInit ("Timer-MapOverlay");
 
lock (%MkOsd); # until end of block
lock (%MkNcDebug); # until end of block
 
# Aktuell gültige Karte
%Map = %{$Maps{'Current'}};
 
if ( &MkOsdIsValid() )
{
# Gueltige OSD Daten
 
# Operation Mode
$OperationMode = "";
if ( &MkIsWptMode() ) { $OperationMode = "WPT"; }
if ( $PlayerMode eq "Play" ) { $OperationMode = "Play"; }
if ( $PlayerMode eq "Pause" ) { $OperationMode = "Paus"; }
if ( $PlayerMode eq "Home" ) { $OperationMode = "Home"; }
if ( &MkIsPhMode() ) { $OperationMode = "PH"; }
if ( &MkIsFreeMode() ) { $OperationMode = "Free"; }
 
my $SatsInUse = $MkOsd{'SatsInUse'};
if ( &CurPosIsValid() and &HomePosIsValid() )
{
# ausreichender GPS Empfang
 
# get x,y map coords of current position
my ($C_x, $C_y, $C_Angel) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $MkOsd{'CompassHeading'});
$MkPos_x = $C_x;
$MkPos_y = $C_y;
 
$System{'CurrPos_x'} = $C_x;
$System{'CurrPos_y'} = $C_y;
 
# rotate MK arrow
my $dy = sin (deg2rad $C_Angel) * ($MapMkLen/2);
my $dx = cos (deg2rad $C_Angel) * ($MapMkLen/2);
my $x0 = $C_x - $dx;
my $y0 = $C_y - $dy;
my $x1 = $C_x + $dx;
my $y1 = $C_y + $dy;
$map_canvas->coords ('MK-Arrow', $x0, $y0, $x1, $y1);
 
# Update speed vector
my $MapAngel = &MapAngel(); # North to Map-Horizont
my $GpsSpeedNorth = $MkNcDebug{'Analog_21'};
my $GpsSpeedEast = $MkNcDebug{'Analog_22'};
my $PhiGpsSpeed = rad2deg atan2 ( $GpsSpeedEast, $GpsSpeedNorth );
$PhiMapSpeed = $PhiGpsSpeed - $MapAngel;
 
# 555 cm/s ~ 20 km/h -> Zeigerlänge = $MkSpeedLen bei 20 km/h
my $dy = sin (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
my $dx = cos (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
my $x0 = $C_x;
my $y0 = $C_y;
my $x1 = $C_x + $dx;
my $y1 = $C_y + $dy;
$map_canvas->coords ('MK-Speed', $x0, $y0, $x1, $y1);
# Update Line between Home and MK
my ($H_x, $H_y) = &MapGps2XY($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'});
$map_canvas->coords ('MK-Home-Line', $H_x, $H_y, $C_x, $C_y);
 
# Update Distance between Home and MK
my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
$MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'} );
my $x = ($C_x - $H_x) / 2 + $H_x + 8;
my $y = ($C_y - $H_y) / 2 + $H_y + 8;
$map_canvas->coords ('MK-Home-Dist', $x, $y);
$map_canvas->itemconfigure ('MK-Home-Dist',
'-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
);
$System{'HomePos_y'} = $H_x;
$System{'HomePos_x'} = $H_y;
$System{'HomeDist'} = $Dist;
$System{'HomeBearing'} = $Bearing;
 
# Update OSD - Sat dependent values
$map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", $MkOsd{'GroundSpeed'} * 0.036) );
 
# Alt = average Luftdruck und Sat
my $Alt = int (&Altitude() + 0.5);
$map_canvas->itemconfigure ('MK-OSD-Alt-Value', '-text' => sprintf ("%3d m", $Alt) );
$System{'Alt'} = $Alt;
 
if ( &TargetIsValid() )
{
# Valid Target
 
# Update Line between Target and MK
my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
$map_canvas->coords ('MK-Target-Line', $C_x, $C_y, $T_x, $T_y);
 
# Update Distance between Target and MK
my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
$MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'} );
$System{'TargetPos_x'} = $T_x;
$System{'TargetPos_y'} = $T_y;
$System{'TargetDist'} = $Dist;
$System{'TargetBearing'} = $Bearing;
 
if ( $Dist >= 25 )
{
my $x = ($C_x - $T_x) / 2 + $T_x - 8;
my $y = ($C_y - $T_y) / 2 + $T_y + 8;
$map_canvas->coords ('MK-Target-Dist', $x, $y);
$map_canvas->itemconfigure ('MK-Target-Dist',
'-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
);
}
else
{
# Don't show distance < 25m
$map_canvas->coords ('MK-Target-Dist', 0, -100);
}
 
# show target icon
my $IconHeight = 48;
my $IconWidth = 48;
$map_canvas->coords('Target', $T_x - $IconWidth/2, $T_y - $IconHeight );
 
$System{'CrossingBorder'} = 0;
if ( &MkIsFlying() and &IsCrossingBorder($MkPos_x, $MkPos_y, $T_x, $T_y) )
{
# only, if MK is flying
$System{'CrossingBorder'} = 1;
&MkMessage ($Translate{'MsgCrossingBorder'}, "Timer-MapOverlay");
}
}
else
{
# No valid Target, move target line out of sight/canvas
$map_canvas->coords ('MK-Target-Line', 0, -100, 0, -100);
$map_canvas->coords ('MK-Target-Dist', 0, -100);
 
# hide target icon
$map_canvas->coords('Target', 0, -100, );
}
 
# Update Line between MK and POI
if ( $PoiMode )
{
my ($P_x, $P_y) = &MapGps2XY($Poi_Lat, $Poi_Lon);
$map_canvas->coords ('MK-POI-Line', $P_x, $P_y, $C_x, $C_y);
 
$System{'PoiPos_x'} = $P_x;
$System{'PoiPos_y'} = $P_y;
}
else
{
$map_canvas->coords ('MK-POI-Line', 0, -200, 0, -200);
}
}
else
{
# kein ausreichender Sat-Empfang
$map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", 0 ) );
}
 
# Update OSD - non Sat dependent values
$map_canvas->itemconfigure ('MK-OSD-Odo-Value', '-text' => sprintf ("%3.3f km", $OdoMeter / 1000) );
$map_canvas->itemconfigure ('MK-OSD-Tim-Value', '-text' => sprintf ("%02d:%02d", $MkFlyingTime / 60, $MkFlyingTime % 60) );
$map_canvas->itemconfigure ('MK-OSD-Sat-Value', '-text' => $MkOsd{'SatsInUse'} );
$map_canvas->itemconfigure ('MK-OSD-Cur-Value', '-text' => sprintf ("%.1f A", $MkOsd{'Current'}) );
 
# Used Capacity
my $UsedCapacityFactor = $Cfg->{'map'}->{'UsedCapacityFactor'} || "1.0";
$System{'UsedCapacity'} = $MkOsd{'UsedCapacity'} * $UsedCapacityFactor;
$map_canvas->itemconfigure ('MK-OSD-Cap-Value', '-text' => sprintf ("%.2f Ah", $System{'UsedCapacity'} / 1000) );
 
# Power
$System{'Power'} = int ($MkOsd{'Current'} * $MkOsd{'UBat'} + 0.5);
$map_canvas->itemconfigure ('MK-OSD-Pow-Value', '-text' => sprintf ("%d W", $System{'Power'} ) );
 
# battery - OSD and warning
my $UBat = sprintf ("%3.1f", $MkOsd{'UBat'});
$System{'UBat'} = $UBat;
$map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-text' => "$UBat V" );
 
$System{'BatWarning'} = 0;
$map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => $Cfg->{'mkcockpit'}->{'ColorOsd'});
if ( $MkOsd{'UBat'} < $Cfg->{'mkcockpit'}->{'UBatWarning'} )
{
if ( time %2 )
{
$map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => 'red');
}
 
&MkMessage ($Translate{'MsgBatWarning'}, "Timer-MapOverlay");
$System{'BatWarning'} = 1;
}
 
 
# Display Operation Mode
my $DisplayMode = $OperationMode;
if ( &MkIsWptMode() and $OperationMode eq "Play" )
{
my %ModeMatrix =
(
"KML-STD" => "Play KML",
"KML-RND" => "Play KML",
"KML-MAP" => "Play KML",
"WPT-STD" => "Play WPT",
"WPT-RND" => "Rand WPT",
"WPT-MAP" => "Rand MAP",
);
my $Key = "${PlayerWptKmlMode}-${PlayerRandomMode}";
$DisplayMode = $ModeMatrix{$Key};
}
 
if ( &MkIsWptMode() and $OperationMode eq "Paus" )
{
$DisplayMode = $DisplayMode . " " . $PlayerPauseMode;
}
 
$System{'RangeWarning'} = 0;
if ( &MkRangeLimit() )
{
$DisplayMode = "$DisplayMode" . " !!"; # Range Warning
$System{'RangeWarning'} = 1;
}
$map_canvas->itemconfigure ('MK-OSD-Mode-Value', '-text' => $DisplayMode );
 
 
# Waypoints abhaengig vom Modus NC/Player
my $WpValue = "-- / --";
if ( $MkOsd{'WaypointNumber'} > 0)
{
$WpValue = sprintf ("%d / %d", $MkOsd{'WaypointIndex'}, $MkOsd{'WaypointNumber'});
}
if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "WPT")
{
$WpValue = sprintf ("%d / %d", $WpPlayerIndex +1, scalar @Waypoints);
}
if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "KML" )
{
my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
my $CurrTime = int ($KmlPlayerIndex * $KmlTimeBase + 0.5);
my $TotTime = int (scalar @KmlTargets * $KmlTimeBase + 0.5);
$WpValue = sprintf ("%02d:%02d / %02d:%02d", $CurrTime / 60, $CurrTime % 60, $TotTime / 60, $TotTime % 60);
}
$map_canvas->itemconfigure ('MK-OSD-Wp-Value', '-text' => "$WpValue");
 
# Recording Mode
my $RecordText = "";
if ( $PlayerRecordMode =~ /REC/i )
{
my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
my $TotTime = int (scalar @KmlTargets * $KmlTimeBase + 0.5);
$RecordText = sprintf ("Recording %02d:%02d", $TotTime / 60, $TotTime % 60);
}
$map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => $RecordText );
 
 
# Farbe MK-Zeiger abhängig vom GPS Empfang
my $MkCol= $Cfg->{'mkcockpit'}->{'ColorMkSatNo'};
if ( $SatsInUse >= 1 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatLow'} ; }
if ( $SatsInUse >= 6 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatGood'}; }
$map_canvas->itemconfigure ('MK-Arrow', '-fill' => $MkCol);
 
 
# Show/Hide SatFix Icon
if ($SatsInUse >= 6 )
{
$map_canvas->coords('Satellite', $MapSizeX-100, 10, );
}
else
{
# move icon out of sight
$map_canvas->coords('Satellite', 0, -100, );
}
 
 
# Variometer Pointer
my $dy = -$MkOsd{'Variometer'} * 10;
$map_canvas->coords('Map-Variometer-Pointer', 5, $MapSizeY/2+$dy, 20, $MapSizeY/2+10+$dy, 20, $MapSizeY/2-10+$dy);
 
#
# System checks
#
 
if ( ! &MkIsMotorOn() ) { &MkMessage ($Translate{'MsgMotorOff'}, "Timer-MapOverlay"); }
if ( ! &MkIsFlying() ) { &MkMessage ($Translate{'MsgNotFlying'}, "Timer-MapOverlay"); }
if ( &MkIsCalibrating() ) { &MkMessage ($Translate{'MsgCalibrate'}, "Timer-MapOverlay"); }
if ( &MkIsMotorStarting() ) { &MkMessage ($Translate{'MsgStart'}, "Timer-MapOverlay") }
if ( &MkEmergencyLanding() ) { &MkMessage ($Translate{'MsgEmergencyLanding'}, "Timer-MapOverlay"); }
if ( &MkRangeLimit() ) { &MkMessage ($Translate{'MsgRangeLimit'}, "Timer-MapOverlay"); }
 
# RC range check
my $RcQuality = $MkOsd{'RC_Quality'};
$System{'RCQuality'} = "";
 
if ( $RcQuality < 100 )
{
$System{'RCQuality'} = "NO";
&MkMessage ($Translate{'MsgRcError'}, "Timer-MapOverlay");
}
elsif ( $RcQuality < 150 )
{
$System{'RCQuality'} = "WEAK";
&MkMessage ($Translate{'MsgRcWarning'}, "Timer-MapOverlay");
}
 
# Sat reception quality
if ( $SatsInUse == 0 )
{
&MkMessage ($Translate{'MsgNoSatReception'}, "Timer-MapOverlay");
}
elsif ( $SatsInUse > 0 and $SatsInUse < 6 )
{
&MkMessage ($Translate{'MsgWeakSatReception'}, "Timer-MapOverlay");
}
 
# MK Border check
$System{'OutsideBorder'} = "0";
if ( &MkIsFlying() and ! &IsInsideBorder($MkPos_x, $MkPos_y) )
{
# only, if MK is flying
$System{'OutsideBorder'} = "1";
&MkMessage ($Translate{'MsgOutsideBorder'}, "Timer-MapOverlay");
}
 
# Show Balloon, when aproaching Target
&TargetMessageShow();
 
}
else
{
# keine aktuellen OSD Daten vom MK verfügbar
&MkMessage ($Translate{'MsgNoData'}, "Timer-MapOverlay");
}
 
 
# Wp-Number input from keyboard
$KbTimer++;
if ( $CbPlayerKey ne "" )
{
# Key pressed
$KbNum = "$KbNum" . "$CbPlayerKey";
 
$CbPlayerKey = "";
$KbTimer = 0;
}
if ( $KbTimer > 7 and $KbNum ne "" )
{
# number complete, set target
my $WpIndex = sprintf ("%d", $KbNum);
&WpTargetSet ($WpIndex - 1);
 
# prepare for next number
$KbNum = "";
}
 
 
# Show System Messages
&MkMessageShow();
 
 
# display transmit status in top status line
my $Color = 'lightgray';
my $Relief = 'flat';
if ( $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i )
{
$Color = 'green';
$Relief = 'sunken';
}
if ($TxExtOn == 0 )
{
$Color = 'red';
$Relief = 'sunken';
}
$map_status_top[0]->configure (-text => "Tx:$Translate{'serialchannel'}",
-background => $Color,
-relief => $Relief,
);
my $Color = 'lightgray';
my $Relief = 'flat';
if ( $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i )
{
$Color = 'green';
$Relief = 'sunken';
}
if ($TxExtOn == 0 )
{
$Color = 'red';
$Relief = 'sunken';
}
$map_status_top[1]->configure (-text => "Tx:$Translate{'externcontrol'}",
-background => $Color,
-relief => $Relief,
);
 
# display event status in lower status line, max. 10 fields
my @Event = sort keys %{$Event};
my $EventIndex = 0;
while ( $EventIndex < 10 )
{
my $Key = pop @Event;
my $Color = 'lightgray';
my $Relief = 'flat';
 
if ( $Key eq "" )
{
# clear unused fields
$map_status_event[$EventIndex]->configure (-text => $Key,
-background => $Color,
-relief => $Relief,
);
$EventIndex ++;
}
 
elsif ( ref $Event->{$Key} ne "" and $Event->{$Key}->{'Active'} =~ /y/i )
{
if ( $EventStat{$Key}{'Status'} eq "ON" )
{
$Color = 'green';
$Relief = 'sunken';
}
elsif ( $EventStat{$Key}{'Status'} eq "OFF" )
{
$Color = 'red';
$Relief = 'sunken';
}
 
$map_status_event[$EventIndex]->configure (-text => $Key,
-background => $Color,
-relief => $Relief,
);
$EventIndex ++;
}
else
{
# don't display disabled events
}
}
 
});
 
#
# Timer: 0.1s - Tracking Anzeige aktualisieren
#
if ( $Cfg->{'track'}->{'Active'} =~ /y/i )
{
$TrackMkCalibrateEdge = 0; # calibrate edge detection
 
$frame_map_top->repeat (100, sub
{
# Clear old messages from this timer
&MkMessageInit ("Timer-Tracking");
 
lock (%MkOsd); # until end of block
lock (%MkTrack); # until end of block
 
# Aktuell gültige Karte
%Map = %{$Maps{'Current'}};
 
# Zeiger neu zeichnen
my $AngelPan = @ServoPos[$MkTrack{'ServoPan'}];
my $AngelTilt = @ServoPos[$MkTrack{'ServoTilt'}];
if ( $AngelPan ne "" and $AngelTilt ne "" )
{
my $Angel = $AngelPan;
if ( $AngelTilt > 90 )
{
$Angel += 180;
}
 
# Pan
my $x0 = $TrackSizeX/2;
my $y0 = $TrackSizeY - 0 - $TrackOffY;
my $x1 = $TrackSizeX/2 - ($TrackPtrLen-22) * cos( deg2rad $Angel);
my $y1 = $TrackSizeY - ($TrackPtrLen-22) * sin (deg2rad $Angel) - $TrackOffY;
$track_canvas->coords ('Track-Ptr-Pan', $x0, $y0, $x1, $y1);
 
# Tilt
my $x0 = $TrackSizeX/2;
my $y0 = $TrackSizeY - 0 - $TrackOffY;
my $x1 = $TrackSizeX/2 - ($TrackPtrLen-22) * cos( deg2rad 180 - $AngelTilt);
my $y1 = $TrackSizeY - ($TrackPtrLen-22) * sin (deg2rad 180 - $AngelTilt) - $TrackOffY;
$track_canvas->coords ('Track-Ptr-Tilt', $x0, $y0, $x1, $y1);
}
 
# Farbe Zeiger abhängig vom GPS Empfang
my $SatsInUse = $MkOsd{'SatsInUse'};
my $TrackPtrCol= 'red';
if ( $SatsInUse >= 1 ) { $TrackPtrCol = 'orange'; }
if ( $SatsInUse >= 6 ) { $TrackPtrCol = 'green'; }
$track_canvas->itemconfigure ('Track-Ptr-Pan', '-fill' => $TrackPtrCol);
 
# tracker coldstart condition at MK calibration, rising edge of signal
if ( &MkIsCalibrating() )
{
if ( $TrackMkCalibrateEdge == 0 )
{
$TrackMkCalibrateEdge = 1;
 
# send coldstart command to tracker
$TrackQueue->enqueue("COLDSTART");
}
}
else
{
$TrackMkCalibrateEdge = 0;
}
});
}
 
 
#
# Timer: 0.5s - Waypoint Player
#
$frame_map_top->repeat (500, sub
{
# Clear old messages from this timer
&MkMessageInit ("Timer-Player");
 
lock (%MkOsd); # until end of block
 
if ( &MkIsWptMode() )
{
# NC is in WPT Mode
 
my $PoiBearing = $Cfg->{'waypoint'}->{'DefaultHeading'};
if ( &CurPosIsValid() and $PoiMode )
{
$PoiBearing = &MapGpsTo ( $MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
$Poi_Lat, $Poi_Lon );
$System{'PoiBearing'} = $PoiBearing;
}
 
if ( $PlayerMode eq "Pause" )
{
if ( $PlayerPause_Lat ne "" and $PlayerPause_Lon ne "" )
{
# Gespeicherte Pausen-Pos senden
&MkFlyTo ( '-lat' => $PlayerPause_Lat,
'-lon' => $PlayerPause_Lon,
'-holdtime' => "60",
'-heading' => $PoiBearing,
'-mode' => "Target",
);
}
}
 
if ( $PlayerMode eq "Home" and &HomePosIsValid() )
{
# Gespeicherte oder eingestellte Home-Pos senden
 
my $Home_Lat = $Map{'Home_Lat'} || $MkOsd{'HomePos_Lat'};
my $Home_Lon = $Map{'Home_Lon'} || $MkOsd{'HomePos_Lon'};
 
&MkFlyTo ( '-lat' => $Home_Lat,
'-lon' => $Home_Lon,
'-holdtime' => "60",
'-heading' => $PoiBearing,
'-mode' => "Target",
);
}
 
 
if ( $PlayerWptKmlMode ne 'WPT' )
{
# not in Wp mode
return;
}
 
 
if ( $PlayerMode eq "Play" )
{
 
if ( $PlayerRandomMode =~ /RND/i or $PlayerRandomMode =~ /STD/i )
{
my $WpCnt = scalar @Waypoints;
if ( $WpCnt > 0 and $WpPlayerIndex < $WpCnt )
{
# Target WP-Pos senden
my $Wp = $Waypoints[$WpPlayerIndex];
my $Wp_Lon = $Wp->{'Pos_Lon'};
my $Wp_Lat = $Wp->{'Pos_Lat'};
if ( $Wp_Lat ne "" and $Wp_Lon ne "" )
{
&MkFlyTo ( '-lat' => $Wp_Lat,
'-lon' => $Wp_Lon,
'-holdtime' => "60",
'-heading' => $PoiBearing,
'-mode' => "Target",
);
}
}
}
 
if ( $PlayerRandomMode =~ /MAP/i )
{
# Target Map-Pos senden
&MkFlyTo ( '-x' => $RandomTarget_x ,
'-y' => $RandomTarget_y ,
'-holdtime' => "60",
'-heading' => $PoiBearing,
'-mode' => "Target",
);
}
# Ziel erreicht?
if ( &WpCheckTargetReached() )
{
&WpTargetNext();
}
}
}
 
# WP Player Holdtime count down
if ( $WpPlayerHoldtime > 0 )
{
$WpPlayerHoldtime --;
}
});
 
 
#
# Timer: variabel - KML Player
#
my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
$KmlTimeBase *= 1000;
 
$frame_map_top->repeat ($KmlTimeBase, sub
{
 
# Clear old messages from this timer
&MkMessageInit ("Timer-KMLPlayer");
 
lock (%MkOsd); # until end of block
 
if ( &CurPosIsValid() and $PlayerRecordMode =~ /REC/i )
{
# record current position
push @KmlTargets, {
'Lat' => $MkOsd{'CurPos_Lat'},
'Lon' => $MkOsd{'CurPos_Lon'},
'Alt' => $MkOsd{'CurPos_Alt'},
};
}
 
 
if ( &MkIsWptMode() and $PlayerMode eq "Play" and $PlayerWptKmlMode eq 'KML')
{
# Play KML
 
# Pause, Home is handled in WPT-Timer
 
my $KmlCnt = scalar @KmlTargets;
if ( $KmlCnt > 0 and $KmlPlayerIndex < $KmlCnt )
{
my $Lat = $KmlTargets[$KmlPlayerIndex]->{'Lat'};
my $Lon = $KmlTargets[$KmlPlayerIndex]->{'Lon'};
my $Alt = $KmlTargets[$KmlPlayerIndex]->{'Alt'};
 
&MkFlyTo ( '-lat' => $Lat,
'-lon' => $Lon,
'-alt' => $Alt,
'-holdtime' => "60",
'-mode' => "Target",
);
 
# proceed to next Target
$KmlPlayerIndex ++;
if ( $KmlPlayerIndex >= $KmlCnt )
{
$KmlPlayerIndex = 0;
}
}
}
 
});
 
 
#
# Timer: 1s
#
$frame_map_top->repeat (1000, sub
{
# Clear old messages from this timer
&MkMessageInit ("Timer-Misc-1s");
 
lock (%MkOsd); # until end of block
 
# Aktuell gültige Karte
%Map = %{$Maps{'Current'}};
 
if ( &MkOsdIsValid() )
{
 
# Heartbeat MK Datenübertragung
if ( time %2 )
{
$map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatLarge', );
}
else
{
$map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatSmall', );
}
 
# Flugzeit aktualisieren
# Flugzeit selber mitzählen, da $MkOsd{'FlyingTime'} immer 0 (0.14b)
if ( &MkIsFlying() )
{
$MkFlyingTime += 1;
}
 
# Update ODO-Meter
if ( &CurPosIsValid() )
{
my $C_Lat = $MkOsd{'CurPos_Lat'};
my $C_Lon = $MkOsd{'CurPos_Lon'};
 
if ( $OdoFirst ne "" )
{
my ($Dist, $Bearing) = &MapGpsTo($C_Lat, $C_Lon, $OdoPos_Lat, $OdoPos_Lon );
$OdoMeter += $Dist;
}
$OdoPos_Lat = $C_Lat;
$OdoPos_Lon = $C_Lon;
$OdoFirst = "1";
}
 
 
# # Estimate remaining flight time
# my $TextTimeLeft = "";
# if ( &MkIsFlying() and $Capacity <= 90 )
# {
# my $MaxTime = 100.0 / (100.0 - $Capacity) * $MkFlyingTime;
# my $TimeLeft = $MaxTime - $MkFlyingTime;
# $TextTimeLeft = sprintf ("(%d min)", int ($TimeLeft / 60.0 + 0.8) );
# }
# $map_canvas->itemconfigure ('MK-OSD-Tim-Left', '-text' => $TextTimeLeft );
 
 
# Footprint
if ( $Cfg->{'mkcockpit'}->{'FootprintLength'} > 0 )
{
if ( &CurPosIsValid() )
{
# neuen Footprint hinten anhaengen
my ($x, $y) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'});
push @Footprint, $x, $y;
}
 
while ( $#Footprint / 2 > $Cfg->{'mkcockpit'}->{'FootprintLength'} )
{
# alte Footprints entfernen
splice @Footprint, 0, 2;
}
 
&FootprintRedraw();
}
 
# show tracking antenna icon
if ( $MkTrack{'HomePos_Lat'} ne "" and $MkTrack{'HomePos_Lon'} ne "" )
{
# show antenna icon
my ($x, $y) = &MapGps2XY($MkTrack{'HomePos_Lat'}, $MkTrack{'HomePos_Lon'});
 
my $IconHeight = 48;
my $IconWidth = 48;
$map_canvas->coords('Track-Antenna', $x - $IconWidth/2, $y - $IconHeight );
}
else
{
# move icon out of sight
$map_canvas->coords('Track-Antenna', 0, -50 );
}
 
if ( $GridIsOn )
{
# redraw Grid on canvas
&GridHide();
&GridShow();
}
 
# fun :-)
if ( int rand (100) == 43 )
{
&TtsSpeak ('LOW', $Translate{'TtsFun'});
}
}
 
});
 
 
#
# Timer: 1s - TTS Sprachausgabe
#
$frame_map_top->repeat (1000, sub
{
# Aktuell gültige Karte
%Map = %{$Maps{'Current'}};
 
lock (%MkOsd); # until end of block
 
my $StatusInterval = $Cfg->{'tts'}->{'StatusInterval'} || "30";
$SpeechTimer ++;
my @TtsMsg;
 
if ( &MkOsdIsValid() )
{
# Gueltige OSD Daten
 
if ( $SpeechTimer % $StatusInterval == 0 )
{
#
# give system status, low prio messages
#
 
# clear old low prio messages
&TtsClear('LOW');
 
# get messages from configuration
for ( $Message = 1; $Message < 10; $Message ++)
{
my $MsgId = sprintf ("Message%d", $Message);
my $Msg = $Cfg->{'tts'}{$MsgId};
if ( $Msg =~ /FLIGHT_TIME/i )
{
# Flight time
my $Min = int ( ($MkFlyingTime +1) / 60);
my $Sec = int ( ($MkFlyingTime +1) % 60);
my $Text = sprintf ("$Translate{'TtsFlightTimeMinSec'}", $Min, $Sec);
if ( $Min == 0 )
{
$Text = sprintf ("$Translate{'TtsFlightTimeSec'}", $Sec);
}
&TtsSpeak ('LOW', "$Text");
}
 
elsif ( $Msg =~ /BATTERY/i )
{
# Battery
my ($Volt, $Tenth) = split '\.', $System{'UBat'};
&TtsSpeak ('LOW', sprintf ("$Translate{'TtsBattery'}", $Volt, $Tenth));
}
 
elsif ( $Msg =~ /ALTITUDE/i )
{
# Altitude
if ( $System{'Alt'} < 0 )
{
&TtsSpeak ('LOW', sprintf ("$Translate{'TtsAltitudeNegative'}", abs($System{'Alt'}) ) );
}
else
{
&TtsSpeak ('LOW', sprintf ("$Translate{'TtsAltitude'}", $System{'Alt'} ) );
}
}
 
elsif ( $Msg =~ /SATELLITES/i )
{
# Satellites
&TtsSpeak ('LOW', sprintf ("$Translate{'TtsSatellite'}", $MkOsd{'SatsInUse'}) );
}
 
elsif ( $Msg =~ /HOME_DIST/i )
{
# Home Distance
&TtsSpeak ('LOW', sprintf ("$Translate{'TtsHomeDist'}", $System{'HomeDist'}) );
}
 
elsif ( $Msg =~ /TARGET_DIST/i )
{
# Target Distance
&TtsSpeak ('LOW', sprintf ("$Translate{'TtsTargetDist'}", $System{'TargetDist'}) );
}
 
elsif ( $Msg ne "" )
{
# text or perl code
&TtsSpeak ('LOW', eval "$Msg" );
}
}
}
 
# high prio messages
if ( $SpeechTimer % 5 == 0 )
{
if ( $System{'BatWarning'} ) { push @TtsMsg, $Translate{'TtsBatteryWarning'}; }
if ( $System{'RCQuality'} eq "WEAK" ) { push @TtsMsg, $Translate{'TtsRcWeak'}; }
if ( $System{'RCQuality'} eq "NO" ) { push @TtsMsg, $Translate{'TtsRcNo'}; }
if ( $System{'CrossingBorder'} ) { push @TtsMsg, $Translate{'TtsCrossingBorder'}; }
if ( $System{'OutsideBorder'} ) { push @TtsMsg, $Translate{'TtsOutsideAirfield'}; }
if ( $System{'RangeWarning'} ) { push @TtsMsg, $Translate{'TtsRange'}; }
}
}
else
{
# no data link
if ( $SpeechTimer % 5 == 0 )
{
push @TtsMsg, $Translate{'TtsNoDataLink'};
}
}
 
# speak high prio messages
if ( scalar @TtsMsg > 0 )
{
# Clear pending messsages
&TtsClear('HIGH');
 
# Speak collected messages
foreach $Msg (@TtsMsg)
{
&TtsSpeak ('HIGH', $Msg);
}
}
});
 
 
 
#
# Event engine
#
 
$frame_map_top->repeat (50, sub # 50ms
{
foreach $Key (keys %{$Event})
{
if ( ref $Event->{$Key} eq "" )
{
# ignore: CreationDate, Version
next;
}
 
my $Active = $Event->{$Key}->{'Active'};
my $Trigger = $Event->{$Key}->{'Trigger'}; # RISE, FALL, TOGGLE_RISE, TOGGLE_FALL, TRUE, FALSE
my $Condition = $Event->{$Key}->{'Condition'}; # Perl commands, Return 1, if Condition is true
my $Action = $Event->{$Key}->{'Action'}; # Perl commands
my $ActionElse = $Event->{$Key}->{'ActionElse'}; # Perl commands
my $Delay = $Event->{$Key}->{'Delay'}; # Action rearm delay in ms
my $Repeat = $Event->{$Key}->{'Repeat'}; # Action repeat time in ms
my $RepeatElse = $Event->{$Key}->{'RepeatElse'}; # ActionElse repeat time in ms
 
my $DoAction = 0;
 
if ( $Active =~ /y/i and $Condition ne "" )
{
 
# Provide info about current Event
%MyEvent = %{$Event->{$Key}};
$MyEvent{'EventName'} = $Key;
 
# lock all shared hashes, which might me used in eval-code
lock (%MkOsd); # until end of block
lock (%MkSerialChannel); # until end of block
lock (%Stick); # until end of block
lock (%MkNcDebug); # until end of block
 
# compile and execute condition at runtime. Return 1, if Condition is true
my $CondStat = eval "$Condition";
if ( $@ ne "" )
{
# print compiler message to STDOUT
print "Event $Key: Condition: $@";
}
 
# current time for Delay, Repeat timing
my ($t0_s, $t0_us) = gettimeofday;
 
# for Condition edge detection
my $LastCondStat = $EventStat{$Key}{'LastCondValue'};
$EventStat{$Key}{'LastCondValue'} = $CondStat;
 
# some flags
my $Rise = (! $LastCondStat and $CondStat);
my $Fall = ( $LastCondStat and ! $CondStat);
my $True = $CondStat;
my $False = ! $CondStat;
 
# Delay time check
my $DelayCheck = 1;
my $t1_s = $EventStat{$Key}{'DelayTime_s'};
my $t1_us = $EventStat{$Key}{'DelayTime_us'};
if ( $Delay ne "" and $t1_s ne "" and $t1_us ne "" )
{
my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
if ( $Diff_ms < $Delay )
{
$DelayCheck = 0;
}
}
 
#
# rising or falling edge
#
if ( ($Trigger eq "RISE" and $Rise) or
($Trigger eq "FALL" and $Fall) )
{
$DoAction = $DelayCheck;
 
if ( $DoAction )
{
$EventStat{$Key}{'DelayTime_s'} = $t0_s;
$EventStat{$Key}{'DelayTime_us'} = $t0_us;
 
# reset Repeat time when event gets active
$EventStat{$Key}{'RepeatTime_s'} = 0;
$EventStat{$Key}{'RepeatTime_us'} = 0;
 
# reset event counter
$EventStat{$Key}{'EventCnt'} = 0;
}
}
 
#
# toggle on rising/falling edge
#
elsif ( $Trigger =~ /TOGGLE/i )
{
$DoAction = 0;
if ( ($Rise and $Trigger eq "TOGGLE_RISE") or
($Fall and $Trigger eq "TOGGLE_FALL") )
{
$DoAction = $DelayCheck;
 
if ( $DoAction )
{
$EventStat{$Key}{'DelayTime_s'} = $t0_s;
$EventStat{$Key}{'DelayTime_us'} = $t0_us;
 
# reset Repeat time when event gets active
$EventStat{$Key}{'RepeatTime_s'} = 0;
$EventStat{$Key}{'RepeatTime_us'} = 0;
 
# reset event counter
$EventStat{$Key}{'EventCnt'} = 0;
}
}
 
if ( $DoAction )
{
$EventStat{$Key}{'ToggleOnOff'} ^= 1;
}
 
$DoAction = $EventStat{$Key}{'ToggleOnOff'};
}
 
 
#
# TRUE Condition
#
elsif ( $Trigger eq "TRUE" )
{
if ( $True )
{
$DoAction = $DelayCheck;
 
if ( $DoAction and $Rise)
{
# reset Repeat time when event gets active
$EventStat{$Key}{'RepeatTime_s'} = 0;
$EventStat{$Key}{'RepeatTime_us'} = 0;
 
# reset event counter
$EventStat{$Key}{'EventCnt'} = 0;
}
}
 
if ( $Fall )
{
# set Delay reference from falling edge
 
if ( $DelayCheck )
{
# timestamp of last falling Condition edge change
$EventStat{$Key}{'DelayTime_s'} = $t0_s;
$EventStat{$Key}{'DelayTime_us'} = $t0_us;
 
# reset event counter
$EventStat{$Key}{'EventCnt'} = 0;
}
}
}
 
#
# FALSE Condition
#
elsif ( $Trigger eq "FALSE" )
{
if ( $False )
{
$DoAction = $DelayCheck;
 
if ( $DoAction and $Fall)
{
# reset Repeat time when event gets active
$EventStat{$Key}{'RepeatTime_s'} = 0;
$EventStat{$Key}{'RepeatTime_us'} = 0;
 
# reset event counter
$EventStat{$Key}{'EventCnt'} = 0;
}
}
 
if ( $Rise )
{
# set Delay reference from rising edge
 
if ( $DelayCheck )
{
# timestamp of last rising Condition edge change
$EventStat{$Key}{'DelayTime_s'} = $t0_s;
$EventStat{$Key}{'DelayTime_us'} = $t0_us;
 
# reset event counter
$EventStat{$Key}{'EventCnt'} = 0;
}
}
}
 
else
{
# unkown, fall through
}
 
 
# undefined Status, neither ON, nor OFF
$EventStat{$Key}{'Status'} = "";
 
#
# Process Action
#
if ( $DoAction and $Action ne "" )
{
$EventStat{$Key}{'Status'} = "ON";
my $Execute = 1;
 
my $t1_s = $EventStat{$Key}{'RepeatTime_s'};
my $t1_us = $EventStat{$Key}{'RepeatTime_us'};
if ( $Repeat ne "" and $t1_s ne "" and $t1_us ne "" )
{
my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
if ( $Diff_ms < $Repeat )
{
$Execute = 0;
}
}
 
if ( $Execute )
{
# Accounting
$MyEvent{'EventCnt'} = $EventStat{$Key}{'EventCnt'} ++;
 
# compile and execute action at runtime
eval "$Action";
if ( $@ ne "" )
{
# print compiler message to STDOUT
print "Event $Key: Action: $@";
}
 
# Timestamp, when Action was executed
$EventStat{$Key}{'RepeatTime_s'} = $t0_s;
$EventStat{$Key}{'RepeatTime_us'} = $t0_us;
 
# reset ActionElse repeat timing
$EventStat{$Key}{'RepeatTimeElse_s'} = 0;
$EventStat{$Key}{'RepeatTimeElse_us'} = 0;
}
}
 
#
# Process ActionElse
#
if ( ! $DoAction and $ActionElse ne "" )
{
$EventStat{$Key}{'Status'} = "OFF";
my $Execute = 1;
 
# check repeat time
my $t1_s = $EventStat{$Key}{'RepeatTimeElse_s'};
my $t1_us = $EventStat{$Key}{'RepeatTimeElse_us'};
 
if ( $RepeatElse ne "" and $t1_s ne "" and $t1_us ne "" )
{
my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
if ( $Diff_ms < $RepeatElse )
{
$Execute = 0;
}
}
 
if ( $Execute )
{
# Accounting
$MyEvent{'EventCnt'} = $EventStat{$Key}{'EventCnt'} ++;
 
# compile and execute action at runtime
eval "$ActionElse";
if ( $@ ne "" )
{
# print compiler message to STDOUT
print "Event $Key: ActionElse: $@";
}
 
# Timestamp, when ActionElse was executed
$EventStat{$Key}{'RepeatTimeElse_s'} = $t0_s;
$EventStat{$Key}{'RepeatTimeElse_us'} = $t0_us;
}
}
}
 
else
{
$EventStat{$Key}{'Status'} = "DISABLED";
}
}
});
 
 
# parse input controls for one output channel
# Syntax: Control1_Reverse,min,max,expo,limit + Control2,min,max,expo,limit + ...
sub ParseControls
{
my ($Channel, $ControlVal, $Expo, $Limit) = @_;
 
# Channel output can be sum of multiple input controls
$ChannelVal = 0;
$ControlVal =~ s/ //g;
@Controls = split '\+', $ControlVal;
foreach $ControlVal (@Controls)
{
my ($Control, $Min, $Max, $ControlExpo, $ControlLimit) = split ',', $ControlVal;
if ( $Min eq "" ) { $Min = -125; }
if ( $Max eq "" ) { $Max = 125; }
 
my ($Control, $Reverse) = split '_', $Control;
my $Val = 0;
 
if ( $Control ne "" )
{
# Joystick Button
if ( $Control =~ /^JoystickButton(\d+)/i )
{
my $Button = $1 - 1;
$Val = $Min;
if ( &JoystickButton($Button) )
{
$Val = $Max;
}
}
 
# Joystick POV Button
elsif ( $Control =~ /^JoystickPov(\d+)/i )
{
my $Angle = $1;
my $Pov = $Stick{'JoystickPov'} / 100;
$Val = $Min;
if ( $Pov == $Angle )
{
$Val = $Max;
}
}
 
# Mouse Button
elsif ( $Control =~ /^MouseButton(\d+)/i )
{
my $Button = $1 - 1;
$Val = $Min;
if ( &MouseButton($Button) )
{
$Val = $Max;
}
}
 
# Serial Channel
elsif ( $Control =~ /^SerialChannel/i )
{
$Val = $MkSerialChannel{$Control};
}
 
# fixed value
elsif ( $Control =~ /^(-*\d+)/i )
{
$Val = $1;
}
 
# analog stick
else
{
# Scale Stick 0..StickRange to -125..0..125
$Val = $Stick{$Control} / $Stick{'StickRange'} * 250 - 125;
if ( $Reverse =~ /Reverse/i )
{
$Val = - $Val;
}
}
 
 
# Expo/Limit for each input control
if ( "ExternControlGas ExternControlHeight" =~ /$Channel/ )
{
$Val += 125; # -125..0..125 -> 0..250
$Val = &ExpoLimit (0, 250, $Val, $ControlExpo, $ControlLimit);
}
else
{
# all other symmetrical channels -125..0..125
$Val = &ExpoLimit (-125, 125, $Val, $ControlExpo, $ControlLimit);
}
 
$ChannelVal += $Val;
}
}
 
# Expo/Limit for output channel
if ( "ExternControlGas ExternControlHeight" =~ /$Channel/ )
{
# asymmetrical channels 0..250
$ChannelVal = &ExpoLimit (0, 255, $ChannelVal, $Expo, $Limit);
$ChannelVal = int ($ChannelVal + 0.5);
$ChannelVal = &CheckUnsignedChar($ChannelVal);
}
else
{
# all other symmetrical channels -125..0..125
$ChannelVal = &ExpoLimit (-125, 125, $ChannelVal, $Expo, $Limit);
$ChannelVal = int ($ChannelVal + 0.5);
$ChannelVal = &CheckSignedChar($ChannelVal);
}
 
return $ChannelVal;
}
 
 
#
# Send Serial Channel + stick button/analog handling
#
 
my $SerialChannelTiming = $Cfg->{'serialchannel'}->{'SerialChannelFrquency'} || 20;
$SerialChannelTiming = int (1 / $SerialChannelTiming * 1000);
if ( $SerialChannelTiming < 10 )
{
$SerialChannelTiming = 10;
}
 
# Timer: variable timing
$frame_map_top->repeat ($SerialChannelTiming, sub
{
# 12 serial Channel
for ( my $ChannelInd = 0; $ChannelInd < 12; $ChannelInd ++)
{
my $Channel = sprintf ("SerialChannel%02d", $ChannelInd + 1); # key for Cfg-hash
 
my $Control = $Cfg->{'serialchannel'}->{$Channel};
if ( $Control ne "" )
{
my $Expo = $Cfg->{'serialchannel'}->{$Channel . "Expo"};
my $Limit = $Cfg->{'serialchannel'}->{$Channel . "Limit"};
my $ChannelVal = &ParseControls($Channel, $Control, $Expo, $Limit);
 
&SerialChannel($ChannelInd, $ChannelVal);
}
}
 
$MkSerialChannel{'SerialChannelSend'} = $Cfg->{'serialchannel'}->{'SerialChannelSend'};
$MkSerialChannel{'SerialChannelTiming'} = $SerialChannelTiming;
 
# send serial Channel to MK, nicht wenn deaktiviert oder WP uebertragen werden
if ( ! $MkSendWp and $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i and $TxExtOn == 1)
{
&SendSerialChannel(); # send values in %MkSerialChannel
}
});
 
 
#
# Extern Control
#
 
my $ExternControlTiming = $Cfg->{'externcontrol'}->{'ExternControlFrquency'} || 20;
$ExternControlTiming = int (1 / $ExternControlTiming * 1000);
if ( $ExternControlTiming < 10 )
{
$ExternControlTiming = 10;
}
 
# Timer: variable timing
$frame_map_top->repeat ($ExternControlTiming, sub
{
foreach my $Channel ( qw(ExternControlRoll ExternControlNick ExternControlGier ExternControlGas ExternControlHeight) )
{
my $Control = $Cfg->{'externcontrol'}->{$Channel};
if ( $Control ne "" )
{
my $Expo = $Cfg->{'externcontrol'}->{$Channel . "Expo"};
my $Limit = $Cfg->{'externcontrol'}->{$Channel . "Limit"};
$MkExternControl{$Channel} = &ParseControls($Channel, $Control, $Expo, $Limit);
}
}
 
$MkExternControl{'ExternControlSend'} = $Cfg->{'externcontrol'}->{'ExternControlSend'};
$MkExternControl{'ExternControlTimimg'} = $ExternControlTiming;
 
# send extern control to MK, nicht wenn deaktiviert oder WP uebertragen werden
if ( ! $MkSendWp and $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i and $TxExtOn == 1 )
{
&SendExternalControl ( '-nick' => $MkExternControl{'ExternControlNick'},
'-roll' => $MkExternControl{'ExternControlRoll'},
'-gier' => $MkExternControl{'ExternControlGier'},
'-gas' => $MkExternControl{'ExternControlGas'},
'-height' => $MkExternControl{'ExternControlHeight'},
'-remotebuttons' => 0,
'-config' => 1,
'-frame' => 1,
);
 
$MkExternControl{'_Timestamp'} = time; # time when command was sent
}
});
 
 
#
# mouse/joystick crosshair move in player pause mode
#
 
$frame_map_top->repeat (50, sub # 50ms
{
if ( $PlayerMode eq 'Pause' and
$PlayerPause_Lat ne "" and $PlayerPause_Lon ne "" )
{
if ( $Stick{'_JoystickTimestamp'} >= time-1 or $Stick{'_MouseTimestamp'} >= time-2 )
{
lock (%MkOsd); # until end of block
 
my $ControlX = $Cfg->{'map'}->{'CrosshairMoveX'};
my $ControlY = $Cfg->{'map'}->{'CrosshairMoveY'};
 
if ( $ControlX ne "" and $ControlY ne "" )
{
my $StickRange = 250;
my $Bias = $StickRange /2;
 
my $ExpoX = $Cfg->{'map'}->{'CrosshairMoveXExpo'};
my $LimitX = $Cfg->{'map'}->{'CrosshairMoveXLimit'};
my $SpeedX = &ParseControls('CrosshairMoveX', $ControlX, $ExpoX, $LimitX);
 
my $ExpoY = $Cfg->{'map'}->{'CrosshairMoveYExpo'};
my $LimitY = $Cfg->{'map'}->{'CrosshairMoveYLimit'};
my $SpeedY = &ParseControls('CrosshairMoveY', $ControlY, $ExpoY, $LimitY);
 
my $BearingTop = &MapAngel() - 90.0;
my $BearingMouse = rad2deg atan2($SpeedX, $SpeedY);
my $Bearing = $BearingTop + $BearingMouse;
 
if ( $PlayerPauseMode eq "MK" )
{
# MK Reference
$Bearing = $MkOsd{'CompassHeading'} + $BearingMouse;
}
 
# max. 30m/s at 50ms frame time
my $Speed = sqrt ($SpeedX * $SpeedX + $SpeedY * $SpeedY);
my $Dist = 40*50/1000 * $Speed / $Bias;
 
($PlayerPause_Lat, $PlayerPause_Lon) = &MapGpsAt($PlayerPause_Lat, $PlayerPause_Lon, $Dist, $Bearing);
 
if ( $SpeedX != 0 or $SpeedY != 0 )
{
$CrosshairTimerCnt = 0;
}
}
}
 
#
# show/update Crosshair for 5 sec after move
#
 
if ( $CrosshairTimerCnt < 5 * 1000/50)
{
&CrosshairShow ($PlayerPause_Lat, $PlayerPause_Lon);
}
elsif ( $CrosshairTimerCnt == 5 * 1000/50)
{
&CrosshairHide();
}
 
$CrosshairTimerCnt ++;
}
});
 
 
__END__