Library globals

Source track_target.nas

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
# This is a small script that adjusts autopilot target values to track
# (fly in formation with) an AI or Multiplayer aircraft.

# Quick start instructions:
#
#
# 1. Copy this file into $FGROOT/data/Nasal (along with the other
#    system nasal scripts.)
#
# 2. Start up FlightGear selecting an airplane with a reasonably configured
#    autopilot that responds to and works with the standard autopilot 
#    dialog box (F11).  The MiG 15 is one that works, the 777-200 works,
#    the Citation Bravo does not work, the default c172 probably does not
#    work, etc.
#
# 3. Take off and establish stable flight.
#
# 4. Open the property browser (File->Browse Internal Properties) and navigate
#    to /ai/models/  Choose one of the available aircraft[] or multiplayer[]
#    entries.  You can look at all those subtrees to find the call sign you
#    want.  Also note that the subtree for each entity has a radar area that
#    will show range and offset from your current heading.
#
# 5. Open a second property browser window (upper left click box in the first
#    property browser window.)  Navigate to /autopilot/target-tracking/
#
# 6. Set "/autopilot/target-tracking/target-root" to point to the entity
#    path you discovered in step #4.  For instance, this should be set to
#    something like /ai/models/multiplayer[2] or /ai/models/aircraft[0]
#
# 7. Set "/autopilot/target-tracking/goal-range-nm" to the follow distance
#    you want.
#
# 8. Set "/autopilot/target-tracking/enable" = 1, this will turn on the radar
#    computation for each ai/multiplayer entity and will tell the tracking
#    script to start updating the autopilot settings.
#
# 9. Open up the autopilot configuration window (F11) and activate any of the
#    heading, pitch, and speed axes.  The script will begin updating the heading
#    bug angle, the "speed with throttle" value, and the "altitude hold" value.
#
# 10. You can choose to mix and match any of the autopilot modes you want, i.e.
#     you could turn off the heading control and turn manually while the system
#     holds speed and altitude for you.
#
# 11. It always helps to have a sensible target arcraft to chase.  You are
#     flying within the turn radius and climb rate limits of your autopilot.
#
#     Don't forget you are pilot in command and at all times responsible for
#     maintaining safe airspeed and altitude.
#
#     Enjoy the ride!


# print("Target Tracking script loading ...");

# script defaults (configurable if you like)
var default_update_period = 0.05;
var default_goal_range_nm = 0.15;
var default_target_root = "/ai/models/aircraft[0]";
var default_min_speed_kt = 120;

# master enable switch
var target_tracking_enable = 0;

# update period
var update_period = default_update_period;

# goal range to acheive when following target
var goal_range_nm = 0;

# minimum speed so we don't drop out of the sky
var min_speed_kt = 0;

# Target property tree root
var target_root = "";

# Loop identifier
var tracker_loop_id = 0;

# Initialize target tracking
var TrackInit = func {
    if (props.globals.getNode("autopilot") == nil)
        return;

    props.globals.initNode("/autopilot/target-tracking/enable", 0, "BOOL");
    props.globals.initNode("/autopilot/target-tracking/update-period", default_update_period, "DOUBLE");
    props.globals.initNode("/autopilot/target-tracking/goal-range-nm", default_goal_range_nm, "DOUBLE");
    props.globals.initNode("/autopilot/target-tracking/min-speed-kt", default_min_speed_kt, "DOUBLE");
    props.globals.initNode("/autopilot/target-tracking/target-root", default_target_root, "STRING");
   
    setlistener("/autopilot/target-tracking/enable", func { startTimer();} );
}

# If enabled, update our AP target values based on the target range,
# bearing, and speed
var TrackUpdate = func(loop_id) {
    # avoid running multiple concurrent timers
    if (tracker_loop_id != loop_id)
        return;

    if (props.globals.getNode("autopilot") == nil)
        return;

    target_tracking_enable = getprop("/autopilot/target-tracking/enable");

    if ( target_tracking_enable == 1 ) {
        update_period = getprop("/autopilot/target-tracking/update-period");

        # refresh user configurable values
        goal_range_nm = getprop("/autopilot/target-tracking/goal-range-nm");
        target_root = getprop("/autopilot/target-tracking/target-root");
        min_speed_kt = getprop("/autopilot/target-tracking/min-speed-kt");

        # force radar debug-mode on (forced radar calculations even if
        # no radar instrument and ai aircraft are out of range
        setprop("/instrumentation/radar/debug-mode", 1);

        my_hdg_prop = sprintf("/orientation/heading-magnetic-deg" );
        my_hdg = getprop(my_hdg_prop);

        my_hdg_true_prop = sprintf("/orientation/heading-deg" );
        my_hdg_true = getprop(my_hdg_true_prop);

        var alt_prop = sprintf("%s/position/altitude-ft", target_root );
        var alt = getprop(alt_prop);
        if ( alt == nil ) {
            print("bad property path: ", alt_prop);
            return;
        }
    
        var speed_prop = sprintf("%s/velocities/true-airspeed-kt", target_root );
        #correct by local IAS/TAS ratio, because autopilot uses IAS
        #I need to calculate my TAS, not taking wind into account (MP velocities/true-airspeed-kt does not as well)
        var northSpeed = getprop("/velocities/speed-north-fps");
        var eastSpeed = getprop("/velocities/speed-east-fps");
        var downSpeed = getprop("/velocities/speed-down-fps");
        var true_airspeed = FPS2KT * math.sqrt(northSpeed*northSpeed + eastSpeed*eastSpeed + downSpeed*downSpeed);
        #take target TAS and multiply it by my own IAS/TAS ratio to get target IAS
        var speedTAS = getprop(speed_prop);
        if ( speedTAS == nil ) {
            print("bad property path: ", speed_prop);
            return;
        }
        var speed = speedTAS * (getprop("/velocities/airspeed-kt") / true_airspeed); 
    
        var range_prop = sprintf("%s/radar/range-nm", target_root );
        var range = getprop(range_prop);
        if ( range == nil ) {
            print("bad property path: ", range_prop);
            return;
        }
    
        var h_offset_prop = sprintf("%s/radar/h-offset", target_root );
        var h_offset = getprop(h_offset_prop);
        if ( h_offset == nil ) {
            print("bad property path: ", h_offset_prop);
            return;
        }

        if ( h_offset > -90 and h_offset < 90 ) {
            # in front of us
            var range_error = range - goal_range_nm;
        } else {
            # behind us
            var range_error = goal_range_nm - range;
        }
        var target_speed = speed + range_error * 100.0;
        if ( !debug.isnan(target_speed) and target_speed < min_speed_kt ) {
            target_speed = min_speed_kt;
        }

        setprop( "/autopilot/settings/target-altitude-ft", alt );
        setprop( "/autopilot/settings/heading-bug-deg", my_hdg + h_offset );
        setprop( "/autopilot/settings/true-heading-deg",
                 my_hdg_true + h_offset );
        if( !debug.isnan(target_speed) ) setprop( "/autopilot/settings/target-speed-kt", target_speed ); #isnan check because I divide by TAS before

        # only keep the timer running when the feature is really enabled
        settimer(func() { TrackUpdate(loop_id); }, update_period );
    }
}

# create and start a new timer to cause our update function to be called periodially
startTimer = func {
    tracker_loop_id += 1;
    TrackUpdate(tracker_loop_id);
 }

settimer(TrackInit, 0);