Subversion Repositories Projects

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
733 rain-er 1
#!/usr/bin/perl
2
#!/usr/bin/perl -d:ptkdb
3
 
4
###############################################################################
5
#
6
# libmktime.pl -  MK Mission Cockpit - Timer for GUI Frontend
7
#
8
# Copyright (C) 2009  Rainer Walther  (rainerwalther-mail@web.de)
9
#
10
# Creative Commons Lizenz mit den Zusaetzen (by, nc, sa)
11
#
12
# Es ist Ihnen gestattet: 
13
#     * das Werk vervielfältigen, verbreiten und öffentlich zugänglich machen
14
#     * Abwandlungen bzw. Bearbeitungen des Inhaltes anfertigen
15
# 
16
# Zu den folgenden Bedingungen:
17
#     * Namensnennung.
18
#       Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
19
#     * Keine kommerzielle Nutzung.
20
#       Dieses Werk darf nicht für kommerzielle Zwecke verwendet werden.
21
#     * Weitergabe unter gleichen Bedingungen.
22
#       Wenn Sie den lizenzierten Inhalt bearbeiten oder in anderer Weise umgestalten,
23
#       verändern oder als Grundlage für einen anderen Inhalt verwenden,
24
#       dürfen Sie den neu entstandenen Inhalt nur unter Verwendung von Lizenzbedingungen
25
#       weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind.
26
# 
27
# Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen, unter welche dieses
28
# Werk fällt, mitteilen. Am Einfachsten ist es, einen Link auf diese Seite einzubinden.
29
# 
30
# Jede der vorgenannten Bedingungen kann aufgehoben werden, sofern Sie die Einwilligung
31
# des Rechteinhabers dazu erhalten.
32
# 
33
# Diese Lizenz lässt die Urheberpersönlichkeitsrechte unberührt.
34
# 
35
# Weitere Details zur Lizenzbestimmung gibt es hier:
36
#   Kurzform: http://creativecommons.org/licenses/by-nc-sa/3.0/de/
37
#   Komplett: http://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode
38
#
39
###############################################################################
40
# 2009-08-09 0.2.5 rw Timer moved from mkcockpit.pl
41
#                     Optional Player home-pos in map configuration
42
# 2009-08-23 0.2.6 rw Tracking-Antenna Icon
43
#                     POI heading control
44
# 2009-10-11 0.2.7 rw Tracker control changed
45
# 2010-02-10 0.4.0 rw Altitude average changed
46
#                     Event Engine timer
47
#                     Serial channel timer
48
#                     External Control rimer
49
#                     Crosshair Timer
50
#                     5s timer changed to 3s
51
#                     Current, UsedCapacity, Power added
52
# 2010-02-13 0.4.1 rw bugfix JoystickButton min-value
53
#
54
###############################################################################
55
 
56
$Version{'libmktimer.pl'}  = "0.4.1 - 2010-02-13";
57
 
58
use Math::Trig;
59
use Time::HiRes qw(gettimeofday);   # http://search.cpan.org/~jhi/Time-HiRes-1.9719/HiRes.pm
60
 
61
#
62
# Timer: 3s
63
#
64
$main->repeat (3000, sub
65
    {
66
    if ( ! $MkSendWp )
67
        {
68
        # Abfragefrequenz OSD und Debug regelmäßig neu einstellen, falls Übertragungsfehler
69
        $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 10) );   # Frequenz OSD Datensatz, * 10ms
70
        $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 10) );   # Frequenz MK Debug Datensatz, * 10ms
71
        $MkSendQueue->enqueue( "v", "$AddrNC", "");   # Version
72
        $MkSendQueue->enqueue( "e", "$AddrNC", "");   # Error Text Request
73
        }
74
 
75
    lock (%MkOsd);              # until end of block
76
 
77
    # Draw Operation Radius Border
78
    $map_canvas->delete('Map-Border-OperatingRadius');
79
    if ( &HomePosIsValid() )
80
        {
81
        my $Radius = $MkOsd{'OperatingRadius'};
82
        my $H_Lat = $MkOsd{'HomePos_Lat'};
83
        my $H_Lon = $MkOsd{'HomePos_Lon'};
84
        my $Angel = &MapAngel();
85
 
86
        my ($T_Lat, $T_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel -90);
87
        my ($R_Lat, $R_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel);
88
        my ($B_Lat, $B_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel + 90);
89
        my ($L_Lat, $L_Lon) = &MapGpsAt ($H_Lat, $H_Lon, $Radius, $Angel + 180);
90
 
91
        my ($T_x, $T_y) = &MapGps2XY ($T_Lat, $T_Lon);
92
        my ($R_x, $R_y) = &MapGps2XY ($R_Lat, $R_Lon);
93
        my ($B_x, $B_y) = &MapGps2XY ($B_Lat, $B_Lon);
94
        my ($L_x, $L_y) = &MapGps2XY ($L_Lat, $L_Lon);
95
 
96
        $map_canvas->createArc ( $L_x, $B_y, $R_x, $T_y,
97
                                 '-tags' => 'Map-Border-OperatingRadius',
98
                                 '-extent' => '359',
99
                                 '-start' => '0',
100
                                 '-style' => 'chord',
101
                                 '-outline' => $Cfg->{'mkcockpit'}->{'ColorAirfield'},
102
                                 '-width' => '1',
103
                               );
104
        $map_canvas->raise('Map-Border-OperatingRadius', 'Map');  # Border above Map
105
        }
106
 
107
    });
108
 
109
 
110
 
111
#       
112
# Timer: 0.1s - Map Overlay aktualisieren
113
#
114
$frame_map_top->repeat (100, sub
115
    {
116
 
117
    # Clear old messages from this timer
118
    &MkMessageInit ("Timer-MapOverlay");
119
 
120
    lock (%MkOsd);              # until end of block
121
    lock (%MkNcDebug);          # until end of block
122
 
123
    # Aktuell gültige Karte
124
    %Map = %{$Maps{'Current'}};
125
 
126
    if ( &MkOsdIsValid() )
127
        {
128
        # Gueltige OSD Daten
129
 
130
        # Operation Mode
131
        $OperationMode = "";
132
        if ( &MkIsWptMode() )         { $OperationMode = "WPT"; }
133
        if ( $PlayerMode eq "Play" )  { $OperationMode = "Play"; }
134
        if ( $PlayerMode eq "Pause" ) { $OperationMode = "Paus"; }
135
        if ( $PlayerMode eq "Home" )  { $OperationMode = "Home"; }
136
        if ( &MkIsPhMode() )          { $OperationMode = "PH"; }
137
        if ( &MkIsFreeMode() )        { $OperationMode = "Free"; }
138
 
139
        my $SatsInUse = $MkOsd{'SatsInUse'};
140
        if ( &CurPosIsValid()  and  &HomePosIsValid() )
141
            {
142
            # ausreichender GPS Empfang
143
 
144
            # get x,y map coords of current position
145
            my ($C_x, $C_y, $C_Angel) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $MkOsd{'CompassHeading'});
146
            $MkPos_x = $C_x;
147
            $MkPos_y = $C_y;
148
 
149
            $System{'CurrPos_x'} = $C_x;
150
            $System{'CurrPos_y'} = $C_y;
151
 
152
            # rotate MK arrow
153
            my $dy = sin (deg2rad $C_Angel) * ($MapMkLen/2);
154
            my $dx = cos (deg2rad $C_Angel) * ($MapMkLen/2);
155
            my $x0 = $C_x - $dx;
156
            my $y0 = $C_y - $dy;
157
            my $x1 = $C_x + $dx;
158
            my $y1 = $C_y + $dy;
159
            $map_canvas->coords ('MK-Arrow', $x0, $y0, $x1, $y1);
160
 
161
            # Update speed vector
162
            my $MapAngel = &MapAngel();   # North to Map-Horizont
163
            my $GpsSpeedNorth = $MkNcDebug{'Analog_21'};
164
            my $GpsSpeedEast  = $MkNcDebug{'Analog_22'};
165
            my $PhiGpsSpeed = rad2deg atan2 ( $GpsSpeedEast, $GpsSpeedNorth );
166
            $PhiMapSpeed = $PhiGpsSpeed - $MapAngel;
167
 
168
            # 555 cm/s ~ 20 km/h -> Zeigerlänge = $MkSpeedLen bei 20 km/h
169
            my $dy = sin (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
170
            my $dx = cos (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
171
            my $x0 = $C_x;
172
            my $y0 = $C_y;
173
            my $x1 = $C_x + $dx;
174
            my $y1 = $C_y + $dy;
175
            $map_canvas->coords ('MK-Speed', $x0, $y0, $x1, $y1);
176
 
177
            # Update Line between Home and MK
178
            my ($H_x, $H_y) = &MapGps2XY($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'});
179
            $map_canvas->coords ('MK-Home-Line', $H_x, $H_y, $C_x, $C_y);
180
 
181
            # Update Distance between Home and MK
182
            my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
183
                                                    $MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'} );
184
            my $x = ($C_x - $H_x) / 2 + $H_x + 8;
185
            my $y = ($C_y - $H_y) / 2 + $H_y + 8;
186
            $map_canvas->coords ('MK-Home-Dist', $x, $y);
187
            $map_canvas->itemconfigure ('MK-Home-Dist',
188
                                        '-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
189
                                       );
190
            $System{'HomePos_y'} = $H_x;
191
            $System{'HomePos_x'} = $H_y;
192
            $System{'HomeDist'} = $Dist;
193
            $System{'HomeBearing'} = $Bearing;
194
 
195
            # Update OSD - Sat dependent values
196
            $map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", $MkOsd{'GroundSpeed'} * 0.036) );
197
 
198
            # Alt = average Luftdruck und Sat
199
            my $Alt = int (&Altitude() + 0.5);
200
            $map_canvas->itemconfigure ('MK-OSD-Alt-Value', '-text' => sprintf ("%3d m", $Alt) );
201
            $System{'Alt'} = $Alt;
202
 
203
            if ( &TargetIsValid() )
204
                {
205
                # Valid Target
206
 
207
                # Update Line between Target and MK
208
                my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
209
                $map_canvas->coords ('MK-Target-Line', $C_x, $C_y, $T_x, $T_y);
210
 
211
                # Update Distance between Target and MK
212
                my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
213
                                                         $MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'} );
214
                $System{'TargetPos_x'} = $T_x;
215
                $System{'TargetPos_y'} = $T_y;
216
                $System{'TargetDist'} = $Dist;
217
                $System{'TargetBearing'} = $Bearing;
218
 
219
                if ( $Dist >= 25 )  
220
                    {
221
                    my $x = ($C_x - $T_x) / 2 + $T_x - 8;
222
                    my $y = ($C_y - $T_y) / 2 + $T_y + 8;
223
                    $map_canvas->coords ('MK-Target-Dist', $x, $y);
224
                    $map_canvas->itemconfigure ('MK-Target-Dist',
225
                                                '-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
226
                                               );
227
                    }
228
                else
229
                    {
230
                    # Don't show distance < 25m
231
                    $map_canvas->coords ('MK-Target-Dist', 0, -100);
232
                    }
233
 
234
                # show target icon
235
                my $IconHeight = 48;
236
                my $IconWidth = 48;
237
                $map_canvas->coords('Target', $T_x - $IconWidth/2, $T_y - $IconHeight );
238
 
239
                $System{'CrossingBorder'} = 0;
240
                if ( &MkIsFlying()  and  &IsCrossingBorder($MkPos_x, $MkPos_y, $T_x, $T_y) )
241
                    {
242
                    # only, if MK is flying
243
                    $System{'CrossingBorder'} = 1;
244
                    &MkMessage ($Translate{'MsgCrossingBorder'}, "Timer-MapOverlay");
245
                    }
246
                }
247
            else
248
                {
249
                # No valid Target, move target line out of sight/canvas
250
                $map_canvas->coords ('MK-Target-Line', 0, -100, 0, -100);
251
                $map_canvas->coords ('MK-Target-Dist', 0, -100);
252
 
253
                # hide target icon
254
                $map_canvas->coords('Target', 0, -100, );
255
                }
256
 
257
            # Update Line between MK and POI
258
            if ( $PoiMode )
259
                {
260
                my ($P_x, $P_y) = &MapGps2XY($Poi_Lat, $Poi_Lon);
261
                $map_canvas->coords ('MK-POI-Line', $P_x, $P_y, $C_x, $C_y);
262
 
263
                $System{'PoiPos_x'} = $P_x;
264
                $System{'PoiPos_y'} = $P_y;
265
                }
266
            else
267
                {
268
                $map_canvas->coords ('MK-POI-Line', 0, -200, 0, -200);
269
                }
270
            }
271
        else
272
            {
273
            # kein ausreichender Sat-Empfang
274
            $map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", 0 ) );
275
            }
276
 
277
        # Update OSD - non Sat dependent values
278
        $map_canvas->itemconfigure ('MK-OSD-Odo-Value', '-text' => sprintf ("%3.3f km", $OdoMeter / 1000) );
279
        $map_canvas->itemconfigure ('MK-OSD-Tim-Value', '-text' => sprintf ("%02d:%02d", $MkFlyingTime / 60, $MkFlyingTime % 60) );
280
        $map_canvas->itemconfigure ('MK-OSD-Sat-Value', '-text' => $MkOsd{'SatsInUse'} );
281
        $map_canvas->itemconfigure ('MK-OSD-Cur-Value', '-text' => sprintf ("%.1f A", $MkOsd{'Current'}) );
282
 
283
        # Used Capacity
284
        my $UsedCapacityFactor = $Cfg->{'map'}->{'UsedCapacityFactor'} || "1.0";
285
        $System{'UsedCapacity'} = $MkOsd{'UsedCapacity'} * $UsedCapacityFactor;
286
        $map_canvas->itemconfigure ('MK-OSD-Cap-Value', '-text' => sprintf ("%.2f Ah", $System{'UsedCapacity'} / 1000) );
287
 
288
        # Power
289
        $System{'Power'} = int ($MkOsd{'Current'} * $MkOsd{'UBat'} + 0.5);
290
        $map_canvas->itemconfigure ('MK-OSD-Pow-Value', '-text' => sprintf ("%d W", $System{'Power'} ) );
291
 
292
        # battery - OSD and warning
293
        my $UBat = sprintf ("%3.1f", $MkOsd{'UBat'});
294
        $System{'UBat'} = $UBat;
295
        $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-text' => "$UBat V" );
296
 
297
        $System{'BatWarning'} = 0;
298
        $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => $Cfg->{'mkcockpit'}->{'ColorOsd'});
299
        if ( $MkOsd{'UBat'}  <  $Cfg->{'mkcockpit'}->{'UBatWarning'} )
300
            {
301
            if ( time %2 )
302
                {
303
                $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => 'red');
304
                }
305
 
306
            &MkMessage ($Translate{'MsgBatWarning'}, "Timer-MapOverlay");
307
            $System{'BatWarning'} = 1;
308
            }
309
 
310
 
311
        # Display Operation Mode
312
        my $DisplayMode = $OperationMode;
313
        if ( &MkIsWptMode()  and  $OperationMode eq "Play" )
314
            {
315
            my %ModeMatrix =
316
               (
317
               "KML-STD" => "Play KML",
318
               "KML-RND" => "Play KML",
319
               "KML-MAP" => "Play KML",
320
               "WPT-STD" => "Play WPT",
321
               "WPT-RND" => "Rand WPT",
322
               "WPT-MAP" => "Rand MAP",
323
               );
324
            my $Key = "${PlayerWptKmlMode}-${PlayerRandomMode}";
325
            $DisplayMode = $ModeMatrix{$Key};
326
            }
327
 
328
        if ( &MkIsWptMode()  and  $OperationMode eq "Paus" )
329
            {
330
            $DisplayMode = $DisplayMode . " " . $PlayerPauseMode;
331
            }
332
 
333
        $System{'RangeWarning'} = 0;
334
        if ( &MkRangeLimit() )
335
            {
336
            $DisplayMode  = "$DisplayMode" . " !!";   # Range Warning
337
            $System{'RangeWarning'} = 1;
338
            }
339
        $map_canvas->itemconfigure ('MK-OSD-Mode-Value', '-text' => $DisplayMode );
340
 
341
 
342
        # Waypoints abhaengig vom Modus NC/Player
343
        my $WpValue = "-- / --";
344
        if ( $MkOsd{'WaypointNumber'} > 0)
345
            {
346
            $WpValue = sprintf ("%d / %d", $MkOsd{'WaypointIndex'}, $MkOsd{'WaypointNumber'});
347
            }
348
        if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "WPT")
349
            {
350
            $WpValue = sprintf ("%d / %d", $WpPlayerIndex +1, scalar @Waypoints);
351
            }
352
        if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "KML" )
353
            {
354
            my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
355
            my $CurrTime = int ($KmlPlayerIndex * $KmlTimeBase + 0.5);
356
            my $TotTime = int (scalar @KmlTargets * $KmlTimeBase + 0.5);
357
            $WpValue = sprintf ("%02d:%02d / %02d:%02d", $CurrTime / 60, $CurrTime % 60, $TotTime / 60, $TotTime % 60);
358
            }
359
        $map_canvas->itemconfigure ('MK-OSD-Wp-Value',  '-text' => "$WpValue");
360
 
361
        # Recording Mode
362
        my $RecordText = "";
363
        if ( $PlayerRecordMode =~ /REC/i )
364
            {
365
            my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
366
            my $TotTime = int (scalar @KmlTargets * $KmlTimeBase + 0.5);
367
            $RecordText = sprintf ("Recording %02d:%02d", $TotTime / 60, $TotTime % 60);
368
            }
369
        $map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => $RecordText );
370
 
371
 
372
        # Farbe MK-Zeiger abhängig vom GPS Empfang
373
        my $MkCol= $Cfg->{'mkcockpit'}->{'ColorMkSatNo'};
374
        if ( $SatsInUse >= 1 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatLow'} ; }
375
        if ( $SatsInUse >= 6 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatGood'}; }
376
        $map_canvas->itemconfigure ('MK-Arrow', '-fill' => $MkCol);
377
 
378
 
379
        # Show/Hide SatFix Icon
380
        if ($SatsInUse >= 6 )
381
            {
382
            $map_canvas->coords('Satellite', $MapSizeX-100, 10, );
383
            }
384
        else
385
            {
386
            # move icon out of sight
387
            $map_canvas->coords('Satellite', 0, -100, );
388
            }
389
 
390
 
391
        # Variometer Pointer
392
        my $dy = -$MkOsd{'Variometer'} * 10;
393
        $map_canvas->coords('Map-Variometer-Pointer', 5, $MapSizeY/2+$dy, 20, $MapSizeY/2+10+$dy, 20, $MapSizeY/2-10+$dy);
394
 
395
        #
396
        # System checks
397
        #
398
 
399
        if ( ! &MkIsMotorOn() )      { &MkMessage ($Translate{'MsgMotorOff'}, "Timer-MapOverlay"); }
400
        if ( ! &MkIsFlying() )       { &MkMessage ($Translate{'MsgNotFlying'}, "Timer-MapOverlay"); }
401
        if ( &MkIsCalibrating() )    { &MkMessage ($Translate{'MsgCalibrate'}, "Timer-MapOverlay"); }
402
        if ( &MkIsMotorStarting() )  { &MkMessage ($Translate{'MsgStart'}, "Timer-MapOverlay") }
403
        if ( &MkEmergencyLanding() ) { &MkMessage ($Translate{'MsgEmergencyLanding'}, "Timer-MapOverlay"); }
404
        if ( &MkRangeLimit() )       { &MkMessage ($Translate{'MsgRangeLimit'}, "Timer-MapOverlay"); }
405
 
406
        # RC range check
407
        my $RcQuality = $MkOsd{'RC_Quality'};
408
        $System{'RCQuality'} = "";
409
 
410
        if ( $RcQuality < 100 )
411
            {
412
            $System{'RCQuality'} = "NO";
413
            &MkMessage ($Translate{'MsgRcError'}, "Timer-MapOverlay");
414
            }
415
        elsif ( $RcQuality < 150 )
416
            {
417
            $System{'RCQuality'} = "WEAK";
418
            &MkMessage ($Translate{'MsgRcWarning'}, "Timer-MapOverlay");
419
            }
420
 
421
        # Sat reception quality
422
        if ( $SatsInUse == 0 )
423
            {
424
            &MkMessage ($Translate{'MsgNoSatReception'}, "Timer-MapOverlay");
425
            }
426
        elsif ( $SatsInUse > 0  and $SatsInUse < 6 )
427
            {
428
            &MkMessage ($Translate{'MsgWeakSatReception'}, "Timer-MapOverlay");
429
            }
430
 
431
        # MK Border check
432
        $System{'OutsideBorder'} = "0";
433
        if ( &MkIsFlying() and ! &IsInsideBorder($MkPos_x, $MkPos_y) )
434
            {
435
            # only, if MK is flying
436
            $System{'OutsideBorder'} = "1";
437
            &MkMessage ($Translate{'MsgOutsideBorder'}, "Timer-MapOverlay");
438
            }
439
 
440
        # Show Balloon, when aproaching Target
441
        &TargetMessageShow();
442
 
443
        }
444
    else
445
        {
446
        # keine aktuellen OSD Daten vom MK verfügbar
447
        &MkMessage ($Translate{'MsgNoData'}, "Timer-MapOverlay");
448
        }
449
 
450
 
451
    # Wp-Number input from keyboard
452
    $KbTimer++;
453
    if ( $CbPlayerKey ne "" )
454
        {
455
        # Key pressed
456
        $KbNum = "$KbNum" . "$CbPlayerKey";
457
 
458
        $CbPlayerKey = "";
459
        $KbTimer = 0;
460
        }
461
    if ( $KbTimer > 7  and $KbNum ne "" )
462
        {
463
        # number complete, set target
464
        my $WpIndex = sprintf ("%d", $KbNum);
465
        &WpTargetSet ($WpIndex - 1);
466
 
467
        # prepare for next number
468
        $KbNum = "";
469
        }
470
 
471
 
472
    # Show System Messages
473
    &MkMessageShow();
474
 
475
 
476
    # display transmit status in top status line
477
    my $Color  = 'lightgray';
478
    my $Relief = 'flat';
479
    if ( $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i )
480
        {
481
        $Color = 'green';
482
        $Relief = 'sunken';
483
        }
484
    if ($TxExtOn == 0 )
485
        {
486
        $Color = 'red';
487
        $Relief = 'sunken';
488
        }
489
    $map_status_top[0]->configure (-text       => "Tx:$Translate{'serialchannel'}",
490
                                   -background => $Color,
491
                                   -relief     => $Relief,
492
                                  );
493
    my $Color = 'lightgray';
494
    my $Relief = 'flat';
495
    if ( $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i )
496
        {
497
        $Color  = 'green';
498
        $Relief = 'sunken';
499
        }
500
    if ($TxExtOn == 0 )
501
        {
502
        $Color  = 'red';
503
        $Relief = 'sunken';
504
        }
505
    $map_status_top[1]->configure (-text       => "Tx:$Translate{'externcontrol'}",
506
                                   -background => $Color,
507
                                   -relief     => $Relief,
508
                                  );
509
 
510
    # display event status in lower status line, max. 10 fields
511
    my @Event = sort keys %{$Event};
512
    my $EventIndex = 0;
513
    while ( $EventIndex < 10 )
514
        {
515
        my $Key = pop @Event;
516
        my $Color = 'lightgray';
517
        my $Relief = 'flat';
518
 
519
        if ( $Key eq "" )
520
            {
521
            # clear unused fields
522
            $map_status_event[$EventIndex]->configure (-text       => $Key,
523
                                                       -background => $Color,
524
                                                       -relief     => $Relief,
525
                                                      );
526
            $EventIndex ++;
527
            }
528
 
529
        elsif ( ref $Event->{$Key} ne ""  and  $Event->{$Key}->{'Active'} =~ /y/i )
530
            {
531
            if ( $EventStat{$Key}{'Status'} eq "ON" )
532
                {
533
                $Color  = 'green';
534
                $Relief = 'sunken';
535
                }
536
            elsif ( $EventStat{$Key}{'Status'} eq "OFF" )
537
                {
538
                $Color  = 'red';
539
                $Relief = 'sunken';
540
                }
541
 
542
            $map_status_event[$EventIndex]->configure (-text       => $Key,
543
                                                       -background => $Color,
544
                                                       -relief     => $Relief,
545
                                                      );
546
            $EventIndex ++;
547
            }
548
         else
549
            {
550
            # don't display disabled events
551
            }
552
        }
553
 
554
    });
555
 
556
#       
557
# Timer: 0.1s - Tracking Anzeige aktualisieren
558
#
559
if ( $Cfg->{'track'}->{'Active'} =~ /y/i )
560
    {
561
    $TrackMkCalibrateEdge = 0;  # calibrate edge detection
562
 
563
    $frame_map_top->repeat (100, sub
564
        {
565
        # Clear old messages from this timer
566
        &MkMessageInit ("Timer-Tracking");    
567
 
568
        lock (%MkOsd);              # until end of block
569
        lock (%MkTrack);            # until end of block
570
 
571
        # Aktuell gültige Karte
572
        %Map = %{$Maps{'Current'}};
573
 
574
        # Zeiger neu zeichnen
575
        my $AngelPan  = @ServoPos[$MkTrack{'ServoPan'}];
576
        my $AngelTilt = @ServoPos[$MkTrack{'ServoTilt'}];
577
        if ( $AngelPan ne "" and $AngelTilt ne "" )
578
            {
579
            my $Angel = $AngelPan;
580
            if ( $AngelTilt > 90 )
581
                {
582
                $Angel += 180;
583
                }
584
 
585
            # Pan
586
            my $x0 = $TrackSizeX/2;    
587
            my $y0 = $TrackSizeY - 0 - $TrackOffY;
588
            my $x1 = $TrackSizeX/2 - ($TrackPtrLen-22) * cos( deg2rad $Angel);
589
            my $y1 = $TrackSizeY   - ($TrackPtrLen-22) * sin (deg2rad $Angel) - $TrackOffY;
590
            $track_canvas->coords ('Track-Ptr-Pan', $x0, $y0, $x1, $y1);
591
 
592
            # Tilt
593
            my $x0 = $TrackSizeX/2;    
594
            my $y0 = $TrackSizeY - 0 - $TrackOffY;
595
            my $x1 = $TrackSizeX/2 - ($TrackPtrLen-22) * cos( deg2rad 180 - $AngelTilt);
596
            my $y1 = $TrackSizeY   - ($TrackPtrLen-22) * sin (deg2rad 180 - $AngelTilt) - $TrackOffY;
597
            $track_canvas->coords ('Track-Ptr-Tilt', $x0, $y0, $x1, $y1);
598
            }
599
 
600
        # Farbe Zeiger abhängig vom GPS Empfang
601
        my $SatsInUse = $MkOsd{'SatsInUse'};
602
        my $TrackPtrCol= 'red';
603
        if ( $SatsInUse >= 1 ) { $TrackPtrCol = 'orange'; }
604
        if ( $SatsInUse >= 6 ) { $TrackPtrCol = 'green'; }
605
        $track_canvas->itemconfigure ('Track-Ptr-Pan', '-fill' => $TrackPtrCol);
606
 
607
        # tracker coldstart condition at MK calibration, rising edge of signal
608
        if ( &MkIsCalibrating() )
609
            {
610
            if ( $TrackMkCalibrateEdge == 0 )
611
                {
612
                $TrackMkCalibrateEdge = 1;
613
 
614
                # send coldstart command to tracker
615
                $TrackQueue->enqueue("COLDSTART");
616
                }
617
            }
618
        else
619
            {
620
            $TrackMkCalibrateEdge = 0;
621
            }
622
        });
623
    }
624
 
625
 
626
#       
627
# Timer: 0.5s - Waypoint Player
628
#
629
$frame_map_top->repeat (500, sub
630
    {
631
    # Clear old messages from this timer
632
    &MkMessageInit ("Timer-Player");        
633
 
634
    lock (%MkOsd);              # until end of block
635
 
636
    if ( &MkIsWptMode() )
637
        {
638
        # NC is in WPT Mode 
639
 
640
        my $PoiBearing = $Cfg->{'waypoint'}->{'DefaultHeading'};
641
        if ( &CurPosIsValid()  and  $PoiMode )
642
            {
643
            $PoiBearing = &MapGpsTo ( $MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
644
                                      $Poi_Lat,  $Poi_Lon );
645
            $System{'PoiBearing'} = $PoiBearing;
646
            }
647
 
648
        if ( $PlayerMode eq "Pause" )
649
            {
650
            if ( $PlayerPause_Lat ne ""  and  $PlayerPause_Lon ne "" )
651
                {
652
                # Gespeicherte Pausen-Pos senden
653
                &MkFlyTo ( '-lat'  => $PlayerPause_Lat,
654
                           '-lon'  => $PlayerPause_Lon,
655
                           '-holdtime' => "60",
656
                           '-heading' => $PoiBearing,
657
                           '-mode' => "Target",
658
                         );
659
                }
660
            }
661
 
662
        if ( $PlayerMode eq "Home" and &HomePosIsValid() )
663
            {
664
            # Gespeicherte oder eingestellte Home-Pos senden
665
 
666
            my $Home_Lat = $Map{'Home_Lat'} || $MkOsd{'HomePos_Lat'};
667
            my $Home_Lon = $Map{'Home_Lon'} || $MkOsd{'HomePos_Lon'};
668
 
669
            &MkFlyTo ( '-lat'  => $Home_Lat,
670
                       '-lon'  => $Home_Lon,
671
                       '-holdtime' => "60",
672
                       '-heading' => $PoiBearing,
673
                       '-mode' => "Target",
674
                     );
675
            }
676
 
677
 
678
        if ( $PlayerWptKmlMode ne 'WPT' )
679
            {
680
            # not in Wp mode
681
            return;
682
            }
683
 
684
 
685
        if ( $PlayerMode eq "Play"  )
686
            {
687
 
688
            if ( $PlayerRandomMode =~ /RND/i  or $PlayerRandomMode =~ /STD/i )
689
                {
690
                my $WpCnt = scalar @Waypoints;
691
                if ( $WpCnt > 0  and  $WpPlayerIndex < $WpCnt )
692
                    {
693
                    # Target WP-Pos senden
694
                    my $Wp = $Waypoints[$WpPlayerIndex];
695
                    my $Wp_Lon = $Wp->{'Pos_Lon'};
696
                    my $Wp_Lat = $Wp->{'Pos_Lat'};
697
                    if ( $Wp_Lat ne ""  and  $Wp_Lon ne "" )
698
                        {
699
                        &MkFlyTo ( '-lat'  => $Wp_Lat,
700
                                   '-lon'  => $Wp_Lon,
701
                                   '-holdtime' => "60",
702
                                   '-heading' => $PoiBearing,
703
                                   '-mode' => "Target",
704
                                 );
705
                        }
706
                    }
707
                }
708
 
709
            if ( $PlayerRandomMode =~ /MAP/i )
710
                {
711
                # Target Map-Pos senden
712
                &MkFlyTo ( '-x'  => $RandomTarget_x ,
713
                           '-y'  => $RandomTarget_y ,
714
                           '-holdtime' => "60",
715
                           '-heading' => $PoiBearing,
716
                           '-mode' => "Target",
717
                         );
718
                }
719
 
720
            # Ziel erreicht?
721
            if ( &WpCheckTargetReached() )
722
                {
723
                &WpTargetNext();
724
                }
725
            }
726
        }
727
 
728
    # WP Player Holdtime count down
729
    if ( $WpPlayerHoldtime > 0  )
730
        {
731
        $WpPlayerHoldtime --;
732
        }
733
    });
734
 
735
 
736
#       
737
# Timer: variabel - KML Player
738
#
739
my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
740
$KmlTimeBase *= 1000;
741
 
742
$frame_map_top->repeat ($KmlTimeBase, sub
743
    {
744
 
745
    # Clear old messages from this timer
746
    &MkMessageInit ("Timer-KMLPlayer");    
747
 
748
    lock (%MkOsd);              # until end of block
749
 
750
    if ( &CurPosIsValid() and $PlayerRecordMode =~ /REC/i )
751
        {
752
        # record current position
753
        push @KmlTargets, {
754
                          'Lat' => $MkOsd{'CurPos_Lat'},
755
                          'Lon' => $MkOsd{'CurPos_Lon'},
756
                          'Alt' => $MkOsd{'CurPos_Alt'},
757
                          };
758
        }
759
 
760
 
761
    if ( &MkIsWptMode() and  $PlayerMode eq "Play"  and  $PlayerWptKmlMode eq 'KML')
762
        {
763
        # Play KML
764
 
765
        # Pause, Home is handled in WPT-Timer
766
 
767
        my $KmlCnt = scalar @KmlTargets;
768
        if ( $KmlCnt > 0  and  $KmlPlayerIndex < $KmlCnt )
769
            {
770
            my $Lat = $KmlTargets[$KmlPlayerIndex]->{'Lat'};
771
            my $Lon = $KmlTargets[$KmlPlayerIndex]->{'Lon'};
772
            my $Alt = $KmlTargets[$KmlPlayerIndex]->{'Alt'};
773
 
774
            &MkFlyTo ( '-lat'             => $Lat,
775
                       '-lon'             => $Lon,
776
                       '-alt'             => $Alt,
777
                       '-holdtime'        => "60",
778
                       '-mode'            => "Target",
779
                     );
780
 
781
            # proceed to next Target
782
            $KmlPlayerIndex ++;
783
            if ( $KmlPlayerIndex >= $KmlCnt )
784
                {
785
                $KmlPlayerIndex = 0;
786
                }
787
            }
788
        }
789
 
790
    });
791
 
792
 
793
#       
794
# Timer: 1s
795
#
796
$frame_map_top->repeat (1000, sub
797
    {
798
    # Clear old messages from this timer
799
    &MkMessageInit ("Timer-Misc-1s");    
800
 
801
    lock (%MkOsd);              # until end of block
802
 
803
    # Aktuell gültige Karte
804
    %Map = %{$Maps{'Current'}};
805
 
806
    if ( &MkOsdIsValid() )
807
        {
808
 
809
        # Heartbeat MK Datenübertragung
810
        if ( time %2 )
811
            {
812
            $map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatLarge', );
813
            }
814
        else
815
            {
816
            $map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatSmall', );
817
            }
818
 
819
        # Flugzeit aktualisieren
820
        # Flugzeit selber mitzählen, da $MkOsd{'FlyingTime'} immer 0 (0.14b)
821
        if ( &MkIsFlying() )
822
            {
823
            $MkFlyingTime += 1;
824
            }
825
 
826
        # Update ODO-Meter
827
        if ( &CurPosIsValid() )
828
            {
829
            my $C_Lat = $MkOsd{'CurPos_Lat'};
830
            my $C_Lon = $MkOsd{'CurPos_Lon'};
831
 
832
            if ( $OdoFirst ne "" )
833
                {
834
                my ($Dist, $Bearing) = &MapGpsTo($C_Lat, $C_Lon, $OdoPos_Lat, $OdoPos_Lon );
835
                $OdoMeter += $Dist;
836
                }
837
            $OdoPos_Lat = $C_Lat;
838
            $OdoPos_Lon = $C_Lon;
839
            $OdoFirst = "1";
840
            }
841
 
842
 
843
        # # Estimate remaining flight time
844
        # my $TextTimeLeft = "";
845
        # if ( &MkIsFlying()  and  $Capacity <= 90 )
846
        #     {
847
        #     my $MaxTime = 100.0 / (100.0 - $Capacity) * $MkFlyingTime;
848
        #     my $TimeLeft = $MaxTime - $MkFlyingTime;
849
        #     $TextTimeLeft = sprintf ("(%d min)", int ($TimeLeft / 60.0 + 0.8) );
850
        #     }
851
        # $map_canvas->itemconfigure ('MK-OSD-Tim-Left', '-text' => $TextTimeLeft );
852
 
853
 
854
        # Footprint
855
        if ( $Cfg->{'mkcockpit'}->{'FootprintLength'} > 0 )
856
            {
857
            if ( &CurPosIsValid() )
858
                {
859
                # neuen Footprint hinten anhaengen
860
                my ($x, $y) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'});
861
                push @Footprint, $x, $y;
862
                }
863
 
864
            while ( $#Footprint / 2  >  $Cfg->{'mkcockpit'}->{'FootprintLength'} )
865
                {
866
                # alte Footprints entfernen
867
                splice @Footprint, 0, 2;
868
                }
869
 
870
            &FootprintRedraw();
871
            }
872
 
873
        # show tracking antenna icon
874
        if ( $MkTrack{'HomePos_Lat'} ne ""  and  $MkTrack{'HomePos_Lon'} ne ""  )
875
            {
876
            # show antenna icon
877
            my ($x, $y) = &MapGps2XY($MkTrack{'HomePos_Lat'}, $MkTrack{'HomePos_Lon'});
878
 
879
            my $IconHeight = 48;
880
            my $IconWidth = 48;
881
            $map_canvas->coords('Track-Antenna', $x - $IconWidth/2, $y - $IconHeight );
882
            }
883
        else
884
            {
885
            # move icon out of sight
886
            $map_canvas->coords('Track-Antenna', 0, -50 );
887
            }
888
 
889
        if ( $GridIsOn )
890
            {
891
            # redraw Grid on canvas
892
            &GridHide();
893
            &GridShow();
894
            }
895
 
896
        # fun :-)
897
        if ( int rand (100) == 43 )
898
            {
899
            &TtsSpeak ('LOW', $Translate{'TtsFun'});
900
            }
901
        }
902
 
903
    });
904
 
905
 
906
#       
907
# Timer: 1s - TTS Sprachausgabe
908
#
909
$frame_map_top->repeat (1000, sub
910
    {
911
    # Aktuell gültige Karte
912
    %Map = %{$Maps{'Current'}};
913
 
914
    lock (%MkOsd);              # until end of block
915
 
916
    my $StatusInterval = $Cfg->{'tts'}->{'StatusInterval'}  || "30";  
917
    $SpeechTimer ++;
918
    my @TtsMsg;
919
 
920
    if ( &MkOsdIsValid() )
921
        {
922
        # Gueltige OSD Daten
923
 
924
        if ( $SpeechTimer % $StatusInterval == 0 )
925
            {
926
            #
927
            # give system status, low prio messages
928
            #
929
 
930
            # clear old low prio messages
931
            &TtsClear('LOW');
932
 
933
            # get messages from configuration
934
            for ( $Message = 1; $Message < 10; $Message ++)
935
                {
936
                my $MsgId = sprintf ("Message%d", $Message);
937
                my $Msg = $Cfg->{'tts'}{$MsgId};
938
 
939
                if ( $Msg =~ /FLIGHT_TIME/i )
940
                    {
941
                    # Flight time
942
                    my $Min = int ( ($MkFlyingTime +1) / 60);
943
                    my $Sec = int ( ($MkFlyingTime +1) % 60);
944
                    my $Text = sprintf ("$Translate{'TtsFlightTimeMinSec'}", $Min, $Sec);
945
                    if ( $Min == 0 )
946
                        {
947
                        $Text = sprintf ("$Translate{'TtsFlightTimeSec'}", $Sec);
948
                        }
949
                   &TtsSpeak ('LOW', "$Text");
950
                   }
951
 
952
                elsif ( $Msg =~ /BATTERY/i )
953
                    {
954
                    # Battery
955
                    my ($Volt, $Tenth) = split '\.', $System{'UBat'};
956
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsBattery'}", $Volt, $Tenth));
957
                    }
958
 
959
                elsif ( $Msg =~ /ALTITUDE/i )
960
                    {
961
                    # Altitude
962
                    if ( $System{'Alt'} < 0 )
963
                        {
964
                        &TtsSpeak ('LOW', sprintf ("$Translate{'TtsAltitudeNegative'}", abs($System{'Alt'}) ) );
965
                        }
966
                    else
967
                        {
968
                        &TtsSpeak ('LOW', sprintf ("$Translate{'TtsAltitude'}", $System{'Alt'} ) );
969
                        }
970
                    }
971
 
972
                elsif ( $Msg =~ /SATELLITES/i )
973
                    {
974
                    # Satellites
975
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsSatellite'}",  $MkOsd{'SatsInUse'}) );
976
                    }
977
 
978
                elsif ( $Msg =~ /HOME_DIST/i )
979
                    {
980
                    # Home Distance
981
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsHomeDist'}",  $System{'HomeDist'}) );
982
                    }
983
 
984
                elsif ( $Msg =~ /TARGET_DIST/i )
985
                    {
986
                    # Target Distance
987
                    &TtsSpeak ('LOW', sprintf ("$Translate{'TtsTargetDist'}",  $System{'TargetDist'}) );
988
                    }
989
 
990
                elsif ( $Msg ne "" )
991
                    {
992
                    # text or perl code
993
                    &TtsSpeak ('LOW', eval "$Msg" );
994
                    }
995
                }
996
            }
997
 
998
       # high prio messages
999
       if ( $SpeechTimer % 5 == 0 )
1000
            {
1001
            if ( $System{'BatWarning'} )          { push @TtsMsg, $Translate{'TtsBatteryWarning'}; }
1002
            if ( $System{'RCQuality'} eq "WEAK" ) { push @TtsMsg, $Translate{'TtsRcWeak'}; }
1003
            if ( $System{'RCQuality'} eq "NO" )   { push @TtsMsg, $Translate{'TtsRcNo'}; }
1004
            if ( $System{'CrossingBorder'} )      { push @TtsMsg, $Translate{'TtsCrossingBorder'}; }
1005
            if ( $System{'OutsideBorder'} )       { push @TtsMsg, $Translate{'TtsOutsideAirfield'}; }
1006
            if ( $System{'RangeWarning'} )        { push @TtsMsg, $Translate{'TtsRange'}; }
1007
            }
1008
        }
1009
    else
1010
        {
1011
        # no data link
1012
        if ( $SpeechTimer % 5 == 0 )
1013
            {
1014
            push @TtsMsg, $Translate{'TtsNoDataLink'};
1015
            }
1016
        }
1017
 
1018
    # speak high prio messages 
1019
    if ( scalar @TtsMsg > 0 )
1020
        {
1021
        # Clear pending messsages
1022
        &TtsClear('HIGH');
1023
 
1024
        # Speak collected messages
1025
        foreach $Msg (@TtsMsg)
1026
            {
1027
            &TtsSpeak ('HIGH', $Msg);
1028
            }
1029
        }
1030
    });
1031
 
1032
 
1033
 
1034
#
1035
#  Event engine
1036
#
1037
 
1038
$frame_map_top->repeat (50, sub     # 50ms
1039
    {
1040
    foreach $Key (keys %{$Event})
1041
        {
1042
        if ( ref $Event->{$Key} eq "" )
1043
            {
1044
            # ignore: CreationDate, Version
1045
            next;
1046
            }
1047
 
1048
        my $Active      = $Event->{$Key}->{'Active'};
1049
        my $Trigger     = $Event->{$Key}->{'Trigger'};       # RISE, FALL, TOGGLE_RISE, TOGGLE_FALL, TRUE, FALSE
1050
        my $Condition   = $Event->{$Key}->{'Condition'};     # Perl commands, Return 1, if Condition is true
1051
        my $Action      = $Event->{$Key}->{'Action'};        # Perl commands
1052
        my $ActionElse  = $Event->{$Key}->{'ActionElse'};    # Perl commands
1053
        my $Delay       = $Event->{$Key}->{'Delay'};         # Action rearm delay in ms
1054
        my $Repeat      = $Event->{$Key}->{'Repeat'};        # Action repeat time in  ms
1055
        my $RepeatElse  = $Event->{$Key}->{'RepeatElse'};    # ActionElse repeat time in ms
1056
 
1057
        my $DoAction = 0;
1058
 
1059
        if ( $Active =~ /y/i  and  $Condition ne "" )
1060
            {
1061
 
1062
            # Provide info about current Event
1063
            %MyEvent = %{$Event->{$Key}};
1064
            $MyEvent{'EventName'} = $Key;
1065
 
1066
            # lock all shared hashes, which might me used in eval-code
1067
            lock (%MkOsd);           # until end of block
1068
            lock (%MkSerialChannel); # until end of block
1069
            lock (%Stick);           # until end of block
1070
            lock (%MkNcDebug);       # until end of block
1071
 
1072
            # compile and execute condition at runtime. Return 1, if Condition is true
1073
            my $CondStat = eval "$Condition";    
1074
            if ( $@ ne "" )
1075
                {
1076
                # print compiler message to STDOUT
1077
                print "Event $Key: Condition: $@";
1078
                }
1079
 
1080
            # current time for Delay, Repeat timing
1081
            my ($t0_s, $t0_us) = gettimeofday;
1082
 
1083
            # for Condition edge detection
1084
            my $LastCondStat = $EventStat{$Key}{'LastCondValue'};
1085
            $EventStat{$Key}{'LastCondValue'} = $CondStat;
1086
 
1087
            # some flags
1088
            my $Rise  = (! $LastCondStat  and    $CondStat);
1089
            my $Fall  = (  $LastCondStat  and  ! $CondStat);
1090
            my $True  =   $CondStat;
1091
            my $False = ! $CondStat;
1092
 
1093
            # Delay time check
1094
            my $DelayCheck = 1;
1095
            my $t1_s  =  $EventStat{$Key}{'DelayTime_s'};
1096
            my $t1_us =  $EventStat{$Key}{'DelayTime_us'};
1097
            if ( $Delay ne "" and  $t1_s ne ""  and  $t1_us ne "" )
1098
                {
1099
                my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
1100
                if ( $Diff_ms < $Delay )
1101
                    {
1102
                    $DelayCheck = 0;
1103
                    }
1104
                }
1105
 
1106
            #
1107
            # rising or falling edge
1108
            #
1109
            if ( ($Trigger eq "RISE" and  $Rise)  or
1110
                 ($Trigger eq "FALL" and  $Fall) )
1111
                {
1112
                $DoAction = $DelayCheck;
1113
 
1114
                if ( $DoAction )
1115
                    {
1116
                    $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
1117
                    $EventStat{$Key}{'DelayTime_us'} = $t0_us;
1118
 
1119
                    # reset Repeat time when event gets active
1120
                    $EventStat{$Key}{'RepeatTime_s'}  = 0;
1121
                    $EventStat{$Key}{'RepeatTime_us'} = 0;
1122
 
1123
                    # reset event counter
1124
                    $EventStat{$Key}{'EventCnt'} = 0;
1125
                    }
1126
                }
1127
 
1128
            #
1129
            # toggle on rising/falling edge
1130
            #
1131
            elsif ( $Trigger =~ /TOGGLE/i )
1132
                {
1133
                $DoAction = 0;
1134
                if ( ($Rise  and  $Trigger eq "TOGGLE_RISE")  or
1135
                     ($Fall  and  $Trigger eq "TOGGLE_FALL") )
1136
                    {
1137
                    $DoAction = $DelayCheck;
1138
 
1139
                    if ( $DoAction )
1140
                        {
1141
                        $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
1142
                        $EventStat{$Key}{'DelayTime_us'} = $t0_us;
1143
 
1144
                        # reset Repeat time when event gets active
1145
                        $EventStat{$Key}{'RepeatTime_s'}  = 0;
1146
                        $EventStat{$Key}{'RepeatTime_us'} = 0;
1147
 
1148
                        # reset event counter
1149
                        $EventStat{$Key}{'EventCnt'} = 0;
1150
                        }
1151
                    }
1152
 
1153
                if ( $DoAction )
1154
                    {
1155
                    $EventStat{$Key}{'ToggleOnOff'} ^= 1;
1156
                    }
1157
 
1158
                $DoAction = $EventStat{$Key}{'ToggleOnOff'};
1159
                }
1160
 
1161
 
1162
            #
1163
            # TRUE Condition
1164
            #
1165
            elsif ( $Trigger eq "TRUE" )
1166
                {
1167
                if ( $True )
1168
                    {
1169
                    $DoAction = $DelayCheck;
1170
 
1171
                    if ( $DoAction and $Rise)
1172
                        {
1173
                        # reset Repeat time when event gets active
1174
                        $EventStat{$Key}{'RepeatTime_s'}  = 0;
1175
                        $EventStat{$Key}{'RepeatTime_us'} = 0;
1176
 
1177
                        # reset event counter
1178
                        $EventStat{$Key}{'EventCnt'} = 0;
1179
                        }
1180
                    }
1181
 
1182
                if ( $Fall )
1183
                    {
1184
                    # set Delay reference from falling edge
1185
 
1186
                    if ( $DelayCheck )
1187
                        {
1188
                        # timestamp of last falling Condition edge change
1189
                        $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
1190
                        $EventStat{$Key}{'DelayTime_us'} = $t0_us;
1191
 
1192
                        # reset event counter
1193
                        $EventStat{$Key}{'EventCnt'} = 0;
1194
                        }
1195
                    }
1196
                }
1197
 
1198
            #
1199
            # FALSE Condition
1200
            #
1201
            elsif ( $Trigger eq "FALSE" )
1202
                {
1203
                if ( $False )
1204
                    {
1205
                    $DoAction = $DelayCheck;
1206
 
1207
                    if ( $DoAction and $Fall)
1208
                        {
1209
                        # reset Repeat time when event gets active
1210
                        $EventStat{$Key}{'RepeatTime_s'}  = 0;
1211
                        $EventStat{$Key}{'RepeatTime_us'} = 0;
1212
 
1213
                        # reset event counter
1214
                        $EventStat{$Key}{'EventCnt'} = 0;
1215
                        }
1216
                    }
1217
 
1218
                if ( $Rise )
1219
                    {
1220
                    # set Delay reference from rising edge
1221
 
1222
                    if ( $DelayCheck )
1223
                        {
1224
                        # timestamp of last rising Condition edge change
1225
                        $EventStat{$Key}{'DelayTime_s'}  = $t0_s;
1226
                        $EventStat{$Key}{'DelayTime_us'} = $t0_us;
1227
 
1228
                        # reset event counter
1229
                        $EventStat{$Key}{'EventCnt'} = 0;
1230
                        }
1231
                    }
1232
                }
1233
 
1234
            else
1235
                {
1236
                # unkown, fall through
1237
                }
1238
 
1239
 
1240
            # undefined Status, neither ON, nor OFF
1241
            $EventStat{$Key}{'Status'} = "";
1242
 
1243
            #
1244
            # Process Action
1245
            #
1246
            if ( $DoAction  and  $Action ne "" )
1247
                {
1248
                $EventStat{$Key}{'Status'} = "ON";
1249
                my $Execute = 1;
1250
 
1251
                my $t1_s  =  $EventStat{$Key}{'RepeatTime_s'};
1252
                my $t1_us =  $EventStat{$Key}{'RepeatTime_us'};
1253
                if ( $Repeat ne "" and  $t1_s ne ""  and  $t1_us ne "" )
1254
                    {
1255
                    my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
1256
                    if ( $Diff_ms < $Repeat )
1257
                        {
1258
                        $Execute = 0;
1259
                        }
1260
                    }
1261
 
1262
                if ( $Execute )
1263
                    {
1264
                    # Accounting
1265
                    $MyEvent{'EventCnt'} = $EventStat{$Key}{'EventCnt'} ++;
1266
 
1267
                    # compile and execute action at runtime
1268
                    eval "$Action";
1269
                    if ( $@ ne "" )
1270
                        {
1271
                        # print compiler message to STDOUT
1272
                        print "Event $Key: Action: $@";
1273
                        }
1274
 
1275
                    # Timestamp, when Action was executed
1276
                    $EventStat{$Key}{'RepeatTime_s'}  = $t0_s;
1277
                    $EventStat{$Key}{'RepeatTime_us'} = $t0_us;
1278
 
1279
                    # reset ActionElse repeat timing
1280
                    $EventStat{$Key}{'RepeatTimeElse_s'}  = 0;
1281
                    $EventStat{$Key}{'RepeatTimeElse_us'} = 0;
1282
                    }
1283
                }
1284
 
1285
            #
1286
            # Process ActionElse
1287
            #
1288
            if (  ! $DoAction  and  $ActionElse ne "" )
1289
                {
1290
                $EventStat{$Key}{'Status'} = "OFF";
1291
                my $Execute = 1;
1292
 
1293
                # check repeat time
1294
                my $t1_s  =  $EventStat{$Key}{'RepeatTimeElse_s'};
1295
                my $t1_us =  $EventStat{$Key}{'RepeatTimeElse_us'};
1296
 
1297
                if ( $RepeatElse ne "" and  $t1_s ne ""  and  $t1_us ne "" )
1298
                    {
1299
                    my $Diff_ms = ($t0_s - $t1_s) * 1000 + ($t0_us - $t1_us) / 1000;
1300
                    if ( $Diff_ms < $RepeatElse )
1301
                        {
1302
                        $Execute = 0;
1303
                        }
1304
                    }
1305
 
1306
                if ( $Execute )
1307
                    {
1308
                    # Accounting
1309
                    $MyEvent{'EventCnt'} = $EventStat{$Key}{'EventCnt'} ++;
1310
 
1311
                    # compile and execute action at runtime
1312
                    eval "$ActionElse";
1313
                    if ( $@ ne "" )
1314
                        {
1315
                        # print compiler message to STDOUT
1316
                        print "Event $Key: ActionElse: $@";
1317
                        }
1318
 
1319
                    # Timestamp, when ActionElse was executed
1320
                    $EventStat{$Key}{'RepeatTimeElse_s'}  = $t0_s;
1321
                    $EventStat{$Key}{'RepeatTimeElse_us'} = $t0_us;
1322
                    }
1323
                }
1324
            }
1325
 
1326
        else
1327
            {
1328
            $EventStat{$Key}{'Status'} = "DISABLED";
1329
            }
1330
        }
1331
    });
1332
 
1333
 
1334
# parse input controls for one output channel
1335
# Syntax: Control1_Reverse,min,max,expo,limit + Control2,min,max,expo,limit + ...
1336
sub ParseControls
1337
    {
1338
    my ($Channel, $ControlVal, $Expo, $Limit) = @_;
1339
 
1340
    # Channel output can be sum of multiple input controls
1341
    $ChannelVal = 0;
1342
    $ControlVal =~ s/ //g;
1343
    @Controls = split '\+', $ControlVal;
1344
    foreach $ControlVal (@Controls)
1345
        {
1346
        my ($Control, $Min, $Max, $ControlExpo, $ControlLimit) = split ',', $ControlVal;
1347
        if ( $Min eq "" ) { $Min = -125; }
1348
        if ( $Max eq "" ) { $Max =  125; }
1349
 
1350
        my ($Control, $Reverse) = split '_', $Control;
1351
        my $Val = 0;
1352
 
1353
        if ( $Control ne "" )
1354
            {
1355
            # Joystick Button
1356
            if ( $Control =~ /^JoystickButton(\d+)/i )
1357
                {
1358
                my $Button = $1 - 1;
1359
                $Val = $Min;
1360
                if ( &JoystickButton($Button) )
1361
                    {
1362
                    $Val = $Max;
1363
                    }
1364
                }
1365
 
1366
            # Joystick POV Button
1367
            elsif ( $Control =~ /^JoystickPov(\d+)/i )
1368
                {
1369
                my $Angle = $1;
1370
                my $Pov = $Stick{'JoystickPov'} / 100;
1371
                $Val = $Min;
1372
                if ( $Pov == $Angle )
1373
                    {
1374
                    $Val = $Max;
1375
                    }
1376
                }
1377
 
1378
            # Mouse Button
1379
            elsif ( $Control =~ /^MouseButton(\d+)/i )
1380
                {
1381
                my $Button = $1 - 1;
1382
                $Val = $Min;
1383
                if ( &MouseButton($Button) )
1384
                    {
1385
                    $Val = $Max;
1386
                    }
1387
                }
1388
 
1389
            # Serial Channel
1390
            elsif ( $Control =~ /^SerialChannel/i )
1391
                {
1392
                $Val = $MkSerialChannel{$Control};
1393
                }
1394
 
1395
            # fixed value
1396
            elsif ( $Control =~ /^(-*\d+)/i )
1397
                {
1398
                $Val = $1;
1399
                }
1400
 
1401
            # analog stick
1402
            else
1403
                {
1404
                # Scale Stick 0..StickRange to -125..0..125
1405
                $Val = $Stick{$Control} / $Stick{'StickRange'} * 250 - 125;
1406
                if ( $Reverse =~ /Reverse/i )
1407
                    {
1408
                    $Val = - $Val;
1409
                    }
1410
                }
1411
 
1412
 
1413
            # Expo/Limit for each input control
1414
            if ( "ExternControlGas ExternControlHeight" =~ /$Channel/ )
1415
                {
1416
                $Val += 125;     # -125..0..125 -> 0..250
1417
                $Val = &ExpoLimit (0, 250, $Val, $ControlExpo, $ControlLimit);
1418
                }
1419
            else
1420
                {
1421
                # all other symmetrical channels -125..0..125
1422
                $Val = &ExpoLimit (-125, 125, $Val, $ControlExpo, $ControlLimit);
1423
                }
1424
 
1425
            $ChannelVal += $Val;
1426
            }
1427
        }
1428
 
1429
    # Expo/Limit for output channel
1430
    if ( "ExternControlGas ExternControlHeight" =~ /$Channel/ )
1431
        {
1432
        # asymmetrical channels 0..250
1433
        $ChannelVal = &ExpoLimit (0, 255, $ChannelVal, $Expo, $Limit);
1434
        $ChannelVal = int ($ChannelVal + 0.5);
1435
        $ChannelVal = &CheckUnsignedChar($ChannelVal);
1436
        }
1437
    else
1438
        {
1439
        # all other symmetrical channels -125..0..125
1440
        $ChannelVal = &ExpoLimit (-125, 125, $ChannelVal, $Expo, $Limit);
1441
        $ChannelVal = int ($ChannelVal + 0.5);
1442
        $ChannelVal = &CheckSignedChar($ChannelVal);
1443
        }
1444
 
1445
    return $ChannelVal;
1446
    }
1447
 
1448
 
1449
#
1450
# Send Serial Channel + stick button/analog handling
1451
#
1452
 
1453
my $SerialChannelTiming = $Cfg->{'serialchannel'}->{'SerialChannelFrquency'} || 20;
1454
$SerialChannelTiming = int (1 / $SerialChannelTiming * 1000);
1455
if ( $SerialChannelTiming < 10 )
1456
    {
1457
    $SerialChannelTiming = 10;
1458
    }
1459
 
1460
# Timer: variable timing
1461
$frame_map_top->repeat ($SerialChannelTiming, sub
1462
    {
1463
    # 12 serial Channel
1464
    for ( my $ChannelInd = 0; $ChannelInd < 12; $ChannelInd ++)
1465
        {
1466
        my $Channel = sprintf ("SerialChannel%02d", $ChannelInd + 1);  # key for Cfg-hash
1467
 
1468
        my $Control = $Cfg->{'serialchannel'}->{$Channel};
1469
        if ( $Control ne "" )
1470
            {
1471
            my $Expo    = $Cfg->{'serialchannel'}->{$Channel . "Expo"};
1472
            my $Limit   = $Cfg->{'serialchannel'}->{$Channel . "Limit"};
1473
            my $ChannelVal = &ParseControls($Channel, $Control, $Expo, $Limit);
1474
 
1475
            &SerialChannel($ChannelInd, $ChannelVal);
1476
            }
1477
        }
1478
 
1479
    $MkSerialChannel{'SerialChannelSend'}   = $Cfg->{'serialchannel'}->{'SerialChannelSend'};
1480
    $MkSerialChannel{'SerialChannelTiming'} = $SerialChannelTiming;
1481
 
1482
    # send serial Channel to MK, nicht wenn deaktiviert  oder WP uebertragen werden
1483
    if ( ! $MkSendWp  and  $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i  and  $TxExtOn == 1)
1484
        {
1485
        &SendSerialChannel();  # send values in %MkSerialChannel
1486
        }
1487
    });
1488
 
1489
 
1490
#
1491
# Extern Control
1492
#
1493
 
1494
my $ExternControlTiming = $Cfg->{'externcontrol'}->{'ExternControlFrquency'} || 20;
1495
$ExternControlTiming = int (1 / $ExternControlTiming * 1000);
1496
if ( $ExternControlTiming < 10 )
1497
    {
1498
    $ExternControlTiming = 10;
1499
    }
1500
 
1501
# Timer: variable timing
1502
$frame_map_top->repeat ($ExternControlTiming, sub
1503
    {
1504
    foreach my $Channel ( qw(ExternControlRoll ExternControlNick ExternControlGier ExternControlGas ExternControlHeight) )
1505
        {
1506
        my $Control = $Cfg->{'externcontrol'}->{$Channel};
1507
        if ( $Control ne "" )
1508
            {
1509
            my $Expo    = $Cfg->{'externcontrol'}->{$Channel . "Expo"};
1510
            my $Limit   = $Cfg->{'externcontrol'}->{$Channel . "Limit"};
1511
            $MkExternControl{$Channel} = &ParseControls($Channel, $Control, $Expo, $Limit);
1512
            }
1513
        }
1514
 
1515
    $MkExternControl{'ExternControlSend'}   = $Cfg->{'externcontrol'}->{'ExternControlSend'};
1516
    $MkExternControl{'ExternControlTimimg'} = $ExternControlTiming;
1517
 
1518
    # send extern control to MK, nicht wenn deaktiviert oder WP uebertragen werden
1519
    if ( ! $MkSendWp  and  $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i  and  $TxExtOn == 1 )
1520
        {
1521
        &SendExternalControl ( '-nick'   => $MkExternControl{'ExternControlNick'},
1522
                               '-roll'   => $MkExternControl{'ExternControlRoll'},
1523
                               '-gier'   => $MkExternControl{'ExternControlGier'},
1524
                               '-gas'    => $MkExternControl{'ExternControlGas'},
1525
                               '-height' => $MkExternControl{'ExternControlHeight'},
1526
                               '-remotebuttons' => 0,
1527
                               '-config' => 1,
1528
                               '-frame'  => 1,
1529
                             );
1530
 
1531
        $MkExternControl{'_Timestamp'} = time;   # time when command was sent
1532
        }
1533
    });
1534
 
1535
 
1536
#
1537
# mouse/joystick crosshair move in player pause mode
1538
#
1539
 
1540
$frame_map_top->repeat (50, sub     # 50ms
1541
    {
1542
    if ( $PlayerMode eq 'Pause'  and
1543
         $PlayerPause_Lat ne ""  and  $PlayerPause_Lon ne "" )
1544
        {
1545
        if ( $Stick{'_JoystickTimestamp'} >= time-1  or  $Stick{'_MouseTimestamp'} >= time-2 )
1546
            {
1547
            lock (%MkOsd);           # until end of block
1548
 
1549
            my $ControlX = $Cfg->{'map'}->{'CrosshairMoveX'};
1550
            my $ControlY = $Cfg->{'map'}->{'CrosshairMoveY'};
1551
 
1552
            if ( $ControlX ne ""  and $ControlY ne "" )
1553
                {
1554
                my $StickRange = 250;
1555
                my $Bias = $StickRange /2;
1556
 
1557
                my $ExpoX    = $Cfg->{'map'}->{'CrosshairMoveXExpo'};
1558
                my $LimitX   = $Cfg->{'map'}->{'CrosshairMoveXLimit'};
1559
                my $SpeedX  = &ParseControls('CrosshairMoveX', $ControlX, $ExpoX, $LimitX);
1560
 
1561
                my $ExpoY    = $Cfg->{'map'}->{'CrosshairMoveYExpo'};
1562
                my $LimitY   = $Cfg->{'map'}->{'CrosshairMoveYLimit'};
1563
                my $SpeedY  = &ParseControls('CrosshairMoveY', $ControlY, $ExpoY, $LimitY);
1564
 
1565
                my $BearingTop = &MapAngel() - 90.0;
1566
                my $BearingMouse = rad2deg atan2($SpeedX, $SpeedY);
1567
                my $Bearing = $BearingTop + $BearingMouse;
1568
 
1569
                if ( $PlayerPauseMode eq "MK" )
1570
                    {
1571
                    # MK Reference
1572
                    $Bearing = $MkOsd{'CompassHeading'} + $BearingMouse;
1573
                    }
1574
 
1575
                # max. 30m/s at 50ms frame time
1576
                my $Speed = sqrt ($SpeedX * $SpeedX + $SpeedY * $SpeedY);
1577
                my $Dist = 40*50/1000 * $Speed / $Bias;
1578
 
1579
                ($PlayerPause_Lat, $PlayerPause_Lon) = &MapGpsAt($PlayerPause_Lat, $PlayerPause_Lon, $Dist, $Bearing);
1580
 
1581
                if ( $SpeedX != 0  or  $SpeedY != 0 )
1582
                    {
1583
                    $CrosshairTimerCnt = 0;
1584
                    }
1585
                }
1586
            }
1587
 
1588
        #
1589
        # show/update Crosshair for 5 sec after move
1590
        #
1591
 
1592
        if ( $CrosshairTimerCnt < 5 * 1000/50)
1593
           {
1594
           &CrosshairShow ($PlayerPause_Lat, $PlayerPause_Lon);
1595
           }
1596
        elsif ( $CrosshairTimerCnt ==  5 * 1000/50)
1597
           {
1598
           &CrosshairHide();
1599
           }
1600
 
1601
        $CrosshairTimerCnt ++;
1602
        }
1603
 
1604
    });
1605
 
1606
 
1607
__END__