You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1779 lines
62 KiB

# script mode (carrier versus runway)
#var mode = "carrier";
var mode = "runway";
#var launch_mode = "power";
var launch_mode = "winch";
var enable_tanker = 0;
var scene_created = 0;
# landing configuration parameters
var gate_dist_nm = 0.25; # nm
var glideslope = 3.0; # degrees
var tz_offset = 40;
if ( mode == "carrier" ) {
glideslope = 3.5;
}
var rotate_speed = 20;
var climbout_alt_ft = 200;
var climbout_speed_kt = 35;
var operational_alt = 500;
var min_speed = 30;
var route_speed = 40;
var max_speed = 60;
var pattern_height_ft = 200;
var flap_speed_kt = 35;
var downwind_speed_kt = 40;
var approach_max_decent_rate = -16.7; # -1000 fpm
var approach_max_climb_rate = 8.333; # 500 fpm
var base_speed_kt = 35;
var gear_down_speed_kt = 35;
var final_speed_kt = min_speed;
var flare_gain = 2; # increase value to flare sooner and more gently
var max_pattern_bank = 30;
var xtrack_route_gain = 1.0;
var xtrack_downwind_gain = 1.0;
var xtrack_final_gain = 1.0;
# circling parameters
var circle_speed = downwind_speed_kt; #kts
var circle_bank = 15; #degrees
# carrier configuration
var carrier_target_speed = 0.0;
var carrier_deck_alt = 75.9; # hard coded in case someone tries to
# init in the air we don't want this
# reset
# internal variables
var time = 0.0;
var timer_start = 0.0;
var last_time = 0.0;
var speedbrake_filter = 0.0;
var last_uas_view = "";
# variables to track carrier heading and speed (probably the self
# reported numbers would actually be good enough??? worth another test
# at some point)
var cc_timer = 0.0;
var carrier_heading = 0.0;
var carrier_speed = 0.0;
var last_carrier_coord = geo.aircraft_position(); # temp init
# variables to track tanker heading and speed
var tanker_timer = 0.0;
var tanker_heading = 0.0;
var tanker_speed = 0.0;
var tanker_coord = geo.aircraft_position(); # temp init
var last_tanker_coord = geo.aircraft_position(); # temp init
var refuel_state = 0;
# variables to auto snap camera for completely coverage
var last_camera_coord = geo.aircraft_position();
# variables to compute our own instantaneous ground speed
var own_timer = 0.0;
var own_groundspeed = 0.0;
var own_coord = geo.aircraft_position();
var last_own_coord = geo.aircraft_position();
# runway variables
var home_apt = nil;
var rwy_coord = geo.aircraft_position(); # temp init
var rwy_heading = 0.0;
var rwy_recip = 0.0;
var xtrack = 0.0; # cross track error (m)
var rwy_dist = 0.0; # distance along runway center line (m)
var last_rwy_dist = 0.0;
var closing_speed_kt = 0.0;
var rwy_gate = geo.aircraft_position(); # temp init
var rwy_course_to = 0;
var rwy_distance_to = 0;
var real_glideslope = 3.0;
# approach variables
var approach_diameter = 0.0;
var approach_45_dist = 0.0;
var approach_dist_to_touchdown = 0.0;
var last_dist_to_touchdown = 0.0;
var approach_dw_alt = 0.0;
var approach_ideal_alt = 0.0;
var approach_alt_error = 0.0;
var approach_lock_glideslope = 0;
var approach_vertspeed_fps = 0;
var circle_coord = geo.aircraft_position(); # temp init
var c1 = geo.aircraft_position();
var c2 = geo.aircraft_position();
c1.set_lon(-123.6618751);
c1.set_lat(37.81747044);
c2.set_lon(-123.6627194);
c2.set_lat(37.81862305);
print("HERE: crs = ", c2.course_to(c1), " dist = ", c2.distance_to(c1) );
var create_scene_if_needed = func() {
var scene_lon = -123.1;
var scene_lat = 37.7;
if ( scene_created ) {
return;
}
var scene_elev = geo.elevation(scene_lat, scene_lon);
if ( scene_elev != nil ) {
var base = geo.aircraft_position();
base.set_lon(scene_lon);
base.set_lat(scene_lat);
# randomize scene location just a bit
base.apply_course_distance( 360.0 * rand(), 1500 + 1000*rand() );
geo.put_model("Models/Aircraft/A380.xml",
base.lat(), base.lon(), scene_elev - (11.0 * .3048),
360.0 * rand(), 0, -5);
# scatter several small life rafts near by
for ( var i = 0; i < 9; i += 1 ) {
var lr = geo.Coord.new( base );
lr.apply_course_distance( 360.0 * rand(), 50 + 250*rand() );
geo.put_model("Models/Maritime/Misc/liferaft4m.ac",
lr.lat(), lr.lon(), scene_elev, 0, 0, -5);
}
# scatter a couple larger life rafts near by
for ( var i = 0; i < 3; i += 1 ) {
var lr = geo.Coord.new( base );
lr.apply_course_distance( 360.0 * rand(), 50 + 250*rand() );
geo.put_model("Models/Maritime/Misc/liferaft8m.ac",
lr.lat(), lr.lon(), scene_elev,
8*rand() - 4, 8*rand() - 4 , 8*rand() - 4);
}
scene_created = 1;
}
}
var update_view = func() {
var current_view = getprop("/sim/current-view/view-number");
var uas_view = getprop("/uas/view-mode");
if ( uas_view == "" or uas_view == nil ) {
setprop("/uas/view-mode", "Gyro Camera");
setprop("/uas/camera-zoom", 1.0);
}
if ( uas_view == last_uas_view ) {
# nothing actually changed, probably just the dialog box refreshin
return;
}
last_uas_view = uas_view;
var view_num = 0;
if ( uas_view == "Cockpit" ) {
view_num = 0;
} elsif ( uas_view == "Gyro Camera" ) {
view_num = view.indexof(uas_view);
} elsif( uas_view == "External" ) {
view_num = 2;
} elsif ( uas_view == "Fly By" ) {
view_num = 6;
} elsif ( uas_view == "PLAT Camera" ) {
view_num = 4;
}
if ( current_view != view_num ) {
setprop("/sim/current-view/view-number", view_num);
}
# manage hud configuration per view
if ( uas_view == "Cockpit" ) {
setprop("/sim/hud/enable3d[1]", 1); # conformal hud for cockpit view
} elsif ( uas_view == "Gyro Camera" ) {
setprop("/sim/hud/enable3d[1]", 0); # non-conformal hud for cam view
} elsif ( uas_view == "External" ) {
setprop("/sim/hud/enable3d[1]", 1); # conformal hud for chase view
} elsif ( uas_view == "Fly By" ) {
setprop("/sim/hud/enable3d[1]", 0); # non-conformal hud for fly-by view
} elsif ( uas_view == "PLAT Camera" ) {
setprop("/sim/hud/enable3d[1]", 0); # non-conformal hud for deck cam
} else {
#setprop("/sim/hud/visibility[1]", 0); # hud off for vanity views
}
}
var update_zoom = func() {
# set zoom from gui if camera view
var zoom = getprop("/uas/camera-zoom");
if ( !zoom ) {
zoom = 1.0; setprop("/uas/camera-zoom", 1.0);
}
var current_view = getprop("/sim/current-view/view-number");
if ( current_view == view.indexof("Gyro Camera") ) {
setprop("/sim/current-view/field-of-view", 55 / zoom );
}
}
var update_camera_target = func() {
var cam_target = getprop("/uas/camera-target");
if ( cam_target == "" or cam_target == nil ) {
setprop("/uas/camera-target", "Carrier");
}
if ( cam_target == "Next Waypoint" ) {
var routesize = getprop("/autopilot/route-manager/route/num");
var cur_wp = getprop("/autopilot/route-manager/current-wp");
if ( cur_wp >= 0 and cur_wp < routesize ) {
var routeNode
= props.globals.getNode("/autopilot/route-manager/route");
var wpNode = routeNode.getChild("wp", cur_wp);
if ( wpNode != nil ) {
var lon = wpNode.getChild("longitude-deg").getValue();
var lat = wpNode.getChild("latitude-deg").getValue();
setprop("/sim/input/click/longitude-deg", lon);
setprop("/sim/input/click/latitude-deg", lat);
var scene_elev = geo.elevation(lat, lon);
if ( scene_elev != nil ) {
setprop("/sim/input/click/altitude-ft", scene_elev*M2FT);
}
}
}
} elsif ( cam_target == "Carrier" ) {
setprop("/sim/input/click/longitude-deg", rwy_coord.lon());
setprop("/sim/input/click/latitude-deg", rwy_coord.lat());
setprop("/sim/input/click/elevation-ft", carrier_deck_alt - 10);
}
}
var update_camera_trigger = func() {
var enable = getprop("/uas/camera-trigger");
if ( enable == nil or !enable ) {
return;
}
var overlap = 0.25;
var agl_m = getprop("/position/altitude-agl-ft") * FT2M;
var fov = getprop("/sim/current-view/field-of-view");
var fov2 = fov * 0.5;
var hdist = math.tan(fov2*D2R) * agl_m * 2.0;
var cur_coord = geo.aircraft_position();
var dist = last_camera_coord.distance_to( cur_coord );
if ( (dist >= hdist * (1.0 - overlap)) and (agl_m > 75) ) {
last_camera_coord.set( cur_coord );
fgcommand("screen-capture");
}
}
var elevator_trim = 0.0;
var elevator_time = 0.0;
var trim_hz = 3.0;
var trim_gain = 0.01;
var airspeed_noise = 0.1;
var emulate_pitch_bobble = func(dt) {
var airspeed = getprop("/velocities/airspeed-kt");
var do_bobble = getprop("/uas/emulate-pitch-bobble");
if ( do_bobble ) {
elevator_time += dt;
var trim = math.sin(elevator_time*math.pi*2*trim_hz)
* trim_gain * rand();
setprop("/controls/flight/elevator-trim", trim);
airspeed += airspeed_noise * (2.0*rand()-1.0);
#print("airspeed = ", airspeed);
}
setprop("/velocities/airspeed-noisy-kt", airspeed);
}
var normdeg = func(a) {
while (a >= 180)
a -= 360;
while (a < -180)
a += 360;
return a;
}
var best_runway = func(apt) {
var wind_speed = getprop("/environment/wind-speed-kt");
var wind_from = wind_speed ? getprop("/environment/wind-from-heading-deg") : 270;
var min = 361;
var rwy = nil;
foreach (var r; keys(apt.runways)) {
var curr = apt.runways[r];
var deviation = math.abs(normdeg(wind_from - curr.heading));
if ( deviation < min ) {
min = deviation;
rwy = curr;
}
#var v = (0.01 * curr.length + 0.01 * curr.width) / deviation;
#if (v > max) {
# max = v;
# rwy = curr;
#}
}
return rwy;
}
# set the runway location and heading manually (used then for
# computing xtrack error, closing speed, etc.)
var set_runway_location_by_ident = func( apt_id = nil ) {
if ( apt_id ) {
home_apt = airportinfo( apt_id );
} else {
home_apt = airportinfo();
}
var rwy = best_runway( home_apt );
setprop("/uas/airport-id", home_apt.id);
setprop("/uas/runway-id", rwy.id);
var m = geo.Coord.new().set_latlon(rwy.lat, rwy.lon);
# m.apply_course_distance(rwy.heading + 180, rwy.length / 2 - rwy.threshold - tz_offset);
print("rwy.length = ", rwy.length);
#m.apply_course_distance(rwy.heading + 180, rwy.length / 2);
var g = geodinfo(m.lat(), m.lon());
m.set_alt( g != nil ? g[0] : apt.elevation );
rwy_coord.set_lon( m.lon() );
rwy_coord.set_lat( m.lat() );
rwy_coord.set_alt( m.alt() );
rwy_heading = rwy.heading;
rwy_recip = rwy_heading + 180.0;
if ( rwy_recip > 360.0 ) {
rwy_recip -= 360.0;
}
}
# set the runway location and heading manually (used then for
# computing xtrack error, closing speed, etc.)
var set_runway_location_by_coord = func(lon_deg, lat_deg, alt_m, heading_deg) {
rwy_coord.set_lon( lon_deg );
rwy_coord.set_lat( lat_deg );
rwy_coord.set_alt( alt_m );
rwy_heading = heading_deg;
rwy_recip = rwy_heading + 180.0;
if ( rwy_recip > 360.0 ) {
rwy_recip -= 360.0;
}
}
# set the runway gate position ... this is a reference point for
# setting up a visual approach to a specific touchdown point and
# heading
var set_runway_gate = func( gate_dist_nm, glideslope_deg ) {
rwy_gate.set( rwy_coord );
rwy_gate.apply_course_distance( rwy_recip, gate_dist_nm*NM2M );
var alt_offset = math.sin(glideslope_deg*D2R) * gate_dist_nm*NM2M;
rwy_gate.set_alt( rwy_coord.alt() + alt_offset );
}
# compute cross track error and runway distance.
var calc_xtrack = func() {
var cur_coord = geo.aircraft_position();
#rwy_coord.dump();
#cur_coord.dump();
rwy_course_to = rwy_coord.course_to( cur_coord );
rwy_distance_to = rwy_coord.distance_to( cur_coord );
var angle = rwy_course_to - rwy_heading;
if ( angle < -180 ) {
angle += 360.0;
} elsif ( angle > 180 ) {
angle -= 360.0;
}
xtrack = math.sin( angle * D2R ) * rwy_distance_to;
rwy_dist = math.cos( angle * D2R ) * rwy_distance_to;
setprop("/uas/xtrack", xtrack);
setprop("/uas/rwy-dist", rwy_dist);
#printf("dist=%.0f brg=%.1f xtrack=%.1f", distance, bearing, xtrack);
}
# compute basic heading and distance to runway
var basic_geo_info = func() {
}
# given the current wind vector and a target ground course, compute
# the required (true) heading
var wind_course = func( course_deg, truespeed, add_carrier_motion ) {
var result = {};
var heading = course_deg;
var groundspeed = 0;
var course_rad = course_deg * D2R;
var winddir = getprop("/environment/wind-from-heading-deg") * D2R;
var windspeed = getprop("/environment/wind-speed-kt");
if ( add_carrier_motion ) {
var angle = 0;
var mps = 0;
# compute wind vector components
angle = 0.5*math.pi - winddir;
mps = windspeed * KT2MPS;
var we = mps * math.cos(angle);
var wn = mps * math.sin(angle);
#print("we = ", we, " wn = ", wn);
# add carrier motion into "effective" wind vector
#var carrier_heading
# = getprop("/ai/models/carrier/orientation/true-heading-deg");
#var carrier_speed
# = getprop("/ai/models/carrier/velocities/speed-kts");
angle = (90 - carrier_heading) * D2R;
mps = carrier_speed * KT2MPS;
var ce = mps * math.cos(angle);
var cn = mps * math.sin(angle);
#print("ce = ", ce, " cn = ", cn);
var te = we + ce;
var tn = wn + cn;
# hack winddir and speed to include carrier motion
winddir = 0.5*math.pi - math.atan2(tn, te);
windspeed = math.sqrt( tn*tn + te*te ) * MPS2KT;
#print("te = ", te, " tn = ", tn);
#print("dir = ", winddir*R2D, " speed = ", windspeed);
}
var swc = (windspeed/truespeed) * math.sin(winddir - course_rad);
if ( math.abs(swc) > 1.0 ) {
# course cannot be flown, wind too strong
} else {
heading = ( course_rad + math.asin(swc) ) * R2D;
if ( heading < 0 ) { heading += 360.0; }
if ( heading > 360 ) { heading -= 360.0; }
groundspeed = truespeed * math.sqrt(1-swc*swc)
- windspeed * math.cos(winddir - course_rad);
if (groundspeed < 0) {
# course cannot be flown-- wind too strong
}
}
result.heading = heading;
result.groundspeed = groundspeed;
return result;
}
var manage_speedbrake = func( target_speed, slop ) {
var own_indspeed
= getprop("/instrumentation/airspeed-indicator/indicated-speed-kt");
var overspeed = own_indspeed - (target_speed + slop);
var speedbrake = 0.0;
if ( overspeed > 0.0 ) {
speedbrake = overspeed / 20;
if ( speedbrake > 1.0 ) { speedbrake = 1.0; }
} else {
speedbrake = 0.0;
}
speedbrake_filter = 0.95 * speedbrake_filter + 0.05 * speedbrake;
setprop("/controls/flight/speedbrake", speedbrake_filter);
}
var compute_approach_metrics = func( dt ) {
var state = getprop("/uas/state");
var airspeed = getprop("/velocities/airspeed-kt");
# compute turning diameter for target downwind speed, if we
# can't get there physically, too bad!
var radius_ft = (downwind_speed_kt*downwind_speed_kt)
/ (11.23*math.tan(0.01745*max_pattern_bank));
var radius_m = radius_ft * FT2M;
approach_diameter = 2.0 * radius_m;
var approach_radius = approach_diameter * 0.5;
setprop("/uas/approach/diameter", approach_diameter);
approach_45_dist = approach_diameter * 1.414; # sqrt(2)
setprop("/uas/approach/forty-five-dist", approach_45_dist);
# degrees off from approach heading
var truespeed = getprop("/instrumentation/airspeed-indicator/true-speed-kt");
var windtri = wind_course( rwy_heading, truespeed, (mode == "carrier") );
var current_heading = getprop("/orientation/heading-deg");
var turn_deg = windtri.heading - current_heading;
if ( turn_deg < -180 ) { turn_deg += 360.0; }
if ( turn_deg > 180 ) { turn_deg -= 360.0; }
var turn_rad = math.abs(turn_deg) * D2R;
setprop("/uas/approach/runway-heading-error", turn_deg);
# distance to fly the remaining turn radius back to the runway heading
var turn_dist_m = turn_rad * approach_radius * 1.5;
var turn_dist_180_m = math.pi * approach_radius;
setprop("/uas/approach/turn-dist", turn_dist_m);
# distance to turn to base
var dist_to_turn = rwy_dist + approach_45_dist;
approach_dist_to_touchdown = 0;
if ( state == "downwind" ) {
# compute approx horizontal distance to touchdown
if ( dist_to_turn > 0 ) {
# short of base turn point
# feather our turn distance down to zero as we approach
# the threshold
var factor = (approach_45_dist - dist_to_turn) / approach_45_dist;
if ( factor > 1.0 ) { factor = 1.0; }
if ( factor < 0.0 ) { factor = 0.0; }
#var turn_diff = turn_dist_180_m - turn_dist_m;
approach_dist_to_touchdown = dist_to_turn + approach_45_dist
+ turn_dist_180_m*(1-factor) + turn_dist_m*factor - math.sin(turn_rad)*approach_radius*factor;
} else {
# past base turn point
approach_dist_to_touchdown = -rwy_dist + turn_dist_m
- math.sin(turn_rad)*approach_radius;
}
} elsif ( state == "base" ) {
approach_dist_to_touchdown = -rwy_dist + turn_dist_m
- math.sin(turn_rad)*approach_radius;
} elsif ( state == "final" ) {
# feather our turn distance down to zero as we approach the threshold
var factor = -rwy_dist / approach_45_dist;
if ( factor > 1.0 ) { factor = 1.0; }
if ( factor < 0.0 ) { factor = 0.0; }
approach_dist_to_touchdown = -rwy_dist + (turn_dist_m
- math.sin(turn_rad)*approach_radius) * factor;
}
# printf("dist to touchdown = %.2f\n", approach_dist_to_touchdown);
setprop("/uas/approach/dist-to-touchdown", approach_dist_to_touchdown);
var groundspeed_kt = getprop("/velocities/groundspeed-kt");
# compute closing speed on touchdown point (keep as positive speed
# so we don't decend when we want to climb.)
var dist = math.abs(last_rwy_dist - rwy_dist);
last_rwy_dist = rwy_dist;
var mps = dist / dt;
closing_speed_kt = 0.05 * closing_speed_kt + 0.95 * mps * MPS2KT; # option a (good for carrier)
# closing_speed_kt = 0.05 * closing_speed_kt + 0.95 * groundspeed_kt; # option b (good for runways?)
setprop("/uas/approach/closing-speed-kt", closing_speed_kt);
# compute optimal decent rate for our target airspeed in zero wind
var approach_tri = wind_course( rwy_heading, final_speed_kt, 0 );
var optimal_decent_kt = math.sin(glideslope*D2R) * final_speed_kt;
var ratio = optimal_decent_kt / approach_tri.groundspeed;
if ( ratio > 1.0 ) { ratio = 1.0; }
if ( ratio < 0.0 ) { ratio = 0.0; }
real_glideslope = math.asin( ratio ) * R2D;
setprop("/uas/approach/real-glideslope", real_glideslope);
# compute optimal decent rate for our closing speed to be on
# the glide slope
var vertspeed_kt = -math.sin(real_glideslope*D2R) * closing_speed_kt;
var vertspeed_mps = vertspeed_kt * KT2MPS;
approach_vertspeed_fps = vertspeed_mps * M2FT;
setprop("/uas/approach/ideal-vertical-rate", approach_vertspeed_fps);
# compute our ideal altitude
approach_ideal_alt = (rwy_coord.alt()
+ math.tan(real_glideslope*D2R)
* approach_dist_to_touchdown) * M2FT;
setprop("/uas/approach/ideal-alt-ft", approach_ideal_alt);
# compute our altitude error
var cur_alt = getprop("/position/altitude-ft");
approach_alt_error = approach_ideal_alt - cur_alt;
setprop("/uas/approach/vert-error", approach_alt_error);
}
var task_circle_current_pos = func() {
var lon = getprop("/position/longitude-deg");
var lat = getprop("/position/latitude-deg");
var ground = getprop("/position/ground-elev-ft");
setprop("/sim/input/click/longitude-deg", lon);
setprop("/sim/input/click/latitude-deg", lat);
setprop("/sim/input/click/elevation-ft", ground );
setprop("/uas/state", "circle");
}
var task_go_home = func() {
setprop("/uas/state", "gohome");
set_runway_location_by_ident( getprop("/uas/airport-id") );
set_runway_gate( gate_dist_nm, glideslope );
}
var task_nasal_launch = func() {
# disable external avior control
setprop("/avior/settings/ap-enable", 0);
setprop("/avior/settings/turret-enable", 0);
# enable internal nasal autopilot system
setprop("/uas/master-switch", 1);
# setup initial state of some key parameters
setprop("/autopilot/locks/heading", "");
setprop("/autopilot/locks/altitude", "");
setprop("/autopilot/locks/speed", "");
setprop("/controls/gear/brake-parking", 1);
setprop("/controls/engines/engine[0]/throttle", 0.0);
setprop("/controls/engines/engine[1]/throttle", 0.0);
setprop("/controls/engines/engine[2]/throttle", 0.0);
setprop("/controls/engines/engine[3]/throttle", 0.0);
setprop("/controls/flight/elevator", 0.0);
setprop("/controls/flight/elevator-trim", 0.0);
# switch to launch mode
setprop("/uas/state", "launch-init");
print("state -> launch-init");
}
var update_state = func( dt ) {
if ( dt < 0.000001 ) {
return;
}
var state = getprop("/uas/state");
create_scene_if_needed();
update_zoom();
update_camera_target();
update_camera_trigger();
emulate_pitch_bobble(dt);
if ( mode == "carrier" ) {
# update runway postion and heading from carrier position
var lon = getprop("/ai/models/carrier/position/longitude-deg");
var lat = getprop("/ai/models/carrier/position/latitude-deg");
var carrier_coord = geo.aircraft_position(); # temp init
carrier_coord.set_lon( lon );
carrier_coord.set_lat( lat );
carrier_coord.set_alt( carrier_deck_alt*FT2M );
# debug
#var cur_coord = geo.aircraft_position();
#var debug_course_to = carrier_coord.course_to( cur_coord );
#var debug_distance_to = carrier_coord.distance_to( cur_coord );
#setprop("/uas/a-course", debug_course_to);
#setprop("/uas/a-distance", debug_distance_to);
var carrier_yaw
= getprop("/ai/models/carrier/orientation/true-heading-deg");
var deck_heading = carrier_yaw - 8;
if ( deck_heading < 0 ) {
deck_heading += 360.0;
}
var td_offset_hdg = carrier_yaw - 178.283;
if ( td_offset_hdg < 0 ) {
td_offset_hdg += 360.0;
}
var td_offset_dist = 151.44;
carrier_coord.apply_course_distance( td_offset_hdg, td_offset_dist );
set_runway_location_by_coord( carrier_coord.lon(), carrier_coord.lat(),
carrier_deck_alt*FT2M, deck_heading );
set_runway_gate( gate_dist_nm, glideslope );
# estimate carrier heading (because the AI system doesn't move
# it exactly at the correct heading ... doh!
cc_timer += dt;
if ( cc_timer > 10 ) {
carrier_heading = last_carrier_coord.course_to( rwy_coord );
var dist = last_carrier_coord.distance_to( rwy_coord );
var mps = dist / cc_timer;
carrier_speed = mps * MPS2KT;
last_carrier_coord.set(rwy_coord);
cc_timer = 0.0;
}
#last_carrier_coord.dump();
#rwy_coord.dump();
#print("rwyhdg = ", rwy_heading, " chdg = ", carrier_heading, " speed = ", carrier_speed);
}
# set 'tower' view paramters
setprop("/sim/tower/longitude-deg", rwy_coord.lon());
setprop("/sim/tower/latitude-deg", rwy_coord.lat());
if ( mode == "carrier" ) {
setprop("/sim/tower/altitude-ft", rwy_coord.alt()*M2FT - 8);
if ( getprop("/sim/current-view/view-number") == 4 ) {
setprop("/sim/current-view/goal-pitch-offset-deg", 3.5);
setprop("/sim/current-view/goal-heading-offset-deg",
carrier_yaw - 22);
}
} else {
setprop("/sim/tower/altitude-ft", rwy_coord.alt()*M2FT + 2);
setprop("/sim/tower/heading-deg", rwy_recip);
if ( getprop("/sim/current-view/view-number") == 4 ) {
setprop("/sim/current-view/goal-pitch-offset-deg", real_glideslope);
# setprop("/sim/current-view/goal-heading-offset-deg", 180.0);
}
}
if ( enable_tanker ) {
var have_tanker = props.globals.getNode("/ai/models/tanker");
if ( have_tanker == nil ) {
tanker.request(); # mostly out in front heading our way
#tanker.request_random(); # random heading and starting point
} else {
# estimate tanker heading (because the AI system doesn't move
# it exactly at the correct heading ... doh!
var tanker_lon = getprop("/ai/models/tanker/position/longitude-deg");
var tanker_lat = getprop("/ai/models/tanker/position/latitude-deg");
var tanker_alt = getprop("/ai/models/tanker/position/altitude-ft");
tanker_coord.set_lon( tanker_lon );
tanker_coord.set_lat( tanker_lat );
tanker_coord.set_alt( tanker_alt );
tanker_timer += dt;
if ( tanker_timer > 1.0 ) {
tanker_heading = last_tanker_coord.course_to( tanker_coord );
var dist = last_tanker_coord.distance_to( tanker_coord );
var mps = dist / tanker_timer;
var new_gs = mps * MPS2KT;
tanker_speed = 0.75 * tanker_speed + 0.25 * new_gs;
last_tanker_coord.set(tanker_coord);
tanker_timer = 0.0;
}
}
}
# compute our own instantaneous ground speed
own_coord = geo.aircraft_position();
own_timer += dt;
if ( own_timer > 0.0001 ) {
var dist = last_own_coord.distance_to( own_coord );
var mps = dist / own_timer;
var new_gs = mps * MPS2KT;
own_groundspeed = 0.9 * own_groundspeed + 0.1 * new_gs;
last_own_coord.set(own_coord);
own_timer = 0.0;
}
calc_xtrack();
if ( state == "init-startup" ) {
setprop("/autopilot/locks/heading", "");
setprop("/autopilot/locks/altitude", "");
setprop("/autopilot/locks/speed", "");
setprop("/controls/gear/brake-parking", 1);
setprop("/controls/engines/engine[0]/throttle", 0.0);
setprop("/controls/engines/engine[1]/throttle", 0.0);
setprop("/controls/engines/engine[2]/throttle", 0.0);
setprop("/controls/engines/engine[3]/throttle", 0.0);
setprop("/controls/flight/elevator", 0.0);
setprop("/controls/flight/elevator-trim", 0.0);
setprop("/uas/state", "ready");
print("state -> ready");
} elsif ( state == "launch-init" ) {
print("Initializing UAS auto-launch system.");
setprop("/sim/freeze/fuel", 1);
setprop("/controls/gear/brake-parking", 1);
setprop("/controls/winch/place", 0);
setprop("/sim/hitches/winch/open", 1);
setprop("/sim/hitches/winch/winch/rel-speed", 0.0);
var coord = geo.aircraft_position();
var heading = getprop("/orientation/heading-deg");
#set_runway_location_by_coord( coord.lon(), coord.lat(), coord.alt(), heading );
set_runway_location_by_ident();
set_runway_gate( gate_dist_nm, glideslope );
setprop("/autopilot/settings/target-yaw-deg", heading);
# setprop("/uas/view-mode", "Fly By");
# turn off carrier "ai" so it just drives straight
setprop("/ai/models/carrier/controls/ai-control", 0);
# set carrier speed
setprop("/ai/models/carrier/controls/tgt-speed-kts",
carrier_target_speed);
# for uav demo, tower view is set to the target touch down
# spot which offers an instructive vantage point (position
# updated elsewhere)
setprop("/sim/tower/auto-position", 0);
approach_lock_glideslope = 0;
refuel_state = 0;
setprop("/uas/state", "launch-init-settle");
print("state -> launch-init-settle");
} elsif ( state == "launch-init-settle" ) {
# give a few seconds for the sim to settle
var time = getprop("/sim/time/elapsed-sec");
if ( time > 10.0 ) {
carrier_deck_alt = getprop("/position/altitude-ft");
setprop("/uas/state", "launch-pretakeoff");
print("state -> launch-pretakeoff");
# turn on wing leveler
setprop("/autopilot/locks/heading", "bank-hold");
setprop("/autopilot/settings/target-bank-deg", 0);
# turn on pitch hold (set to ground attitude for takeoff run)
#setprop("/autopilot/locks/altitude", "pitch-hold");
#var pitch = getprop("/orientation/pitch-deg");
#setprop("/autopilot/settings/target-pitch-deg", pitch + 1);
setprop("/autopilot/locks/altitude", "throttle");
var target_alt
= int((carrier_deck_alt + operational_alt) / 100) * 100;
setprop("/autopilot/settings/target-altitude-ft", target_alt);
setprop("/uas/flight-altitude-ft", target_alt);
}
} elsif ( state == "launch-pretakeoff" ) {
setprop("/controls/flight/wing-fold", 0);
if ( mode == "carrier" ) {
setprop("/controls/gear/launchbar", 1);
}
var wingpos = getprop("/canopy/position-norm");
if ( wingpos == nil ) { wingpos = 0.0; }
if ( wingpos < 0.0001 ) {
setprop("/controls/flight/flaps", 0.36);
setprop("/controls/flight/flapscommand", 1);
setprop("/uas/state", "launch-takeoff");
print("state -> launch-takeoff");
}
} elsif ( state == "launch-takeoff" ) {
if ( launch_mode == "winch" ) {
setprop("/autopilot/locks/altitude", "throttle");
setprop("/autopilot/locks/speed", "elevator");
setprop("/controls/winch/place", 1);
setprop("/uas/state", "launch-takeoff-winch");
print("state -> launch-takeoff-winch");
}
# advance throttle to full over 2 seconds
var throttle = getprop("/controls/engines/engine[0]/throttle");
throttle += dt * 0.5;
if ( throttle > 1.0 ) {
throttle = 1.0;
}
setprop("/controls/engines/engine[0]/throttle", throttle);
setprop("/controls/engines/engine[1]/throttle", throttle);
setprop("/controls/engines/engine[2]/throttle", throttle);
setprop("/controls/engines/engine[3]/throttle", throttle);
# release parking brake after engines have started to spool up
if ( throttle > 0.5 ) {
setprop("/controls/gear/brake-parking", 0);
}
var airspeed = getprop("/velocities/airspeed-kt");
if ( mode == "carrier" ) {
# once engines spool up, set climbout pitch and launch!
var n1 = getprop("/engines/engine[0]/n1");
if ( n1 >= 100 ) {
setprop("/controls/gear/catapult-launch-cmd", 1);
}
} elsif ( mode == "runway" ) {
var groundspeed = getprop("/velocities/groundspeed-kt");
if ( groundspeed > 1.0 ) {
# once we are rolling, turn on steering hold (set to
# current heading for t-o run)
var yawmode = getprop("/autopilot/locks/yaw");
if ( yawmode != "rudder-hold" ) {
# target heading was set earlier in "init" phase
setprop("/autopilot/locks/yaw", "rudder-hold");
}
}
if ( airspeed > 5 and airspeed < 70 ) {
# use differential braking for lower speed ground steering
rudder = getprop("/controls/flight/rudder");
if ( rudder >= 0 ) {
setprop("/controls/gear/brake-right", rudder / 80.0);
} else {
setprop("/controls/gear/brake-left", -rudder / 80.0);
}
} else {
setprop("/controls/gear/brake-right", 0.0);
setprop("/controls/gear/brake-left", 0.0);
}
if ( airspeed > rotate_speed ) {
# begin to rotate
var pitch = getprop("/autopilot/settings/target-pitch-deg");
pitch += dt * 4.0;
if ( pitch > 10.0 ) {
pitch = 10.0;
}
setprop("/autopilot/settings/target-pitch-deg", pitch);
}
}
# adjust target heading to compensate for xtrack error
var speed_gain = 0;
if ( airspeed > 0 ) {
speed_gain = 1 - (airspeed/100);
}
if ( speed_gain < 0.05 ) { speed_gain = 0.05; }
if ( speed_gain > 1.0 ) { speed_gain = 1.0; }
var xtrack_comp = xtrack * speed_gain * 1;
if ( xtrack_comp < -45 ) { xtrack_comp = -45; }
if ( xtrack_comp > 45 ) { xtrack_comp = 45; }
var target_heading = rwy_heading - xtrack_comp;
var current_heading = getprop("/orientation/heading-deg");
var yaw_error = current_heading - target_heading;
if ( yaw_error < -180 ) { yaw_error += 360.0; }
if ( yaw_error > 180 ) { yaw_error -= 360.0; }
setprop("/autopilot/internal/yaw-error-deg", yaw_error);
var agl = getprop("/position/altitude-agl-ft");
var wow = 0;
wow += getprop("/gear/gear[0]/wow");
wow += getprop("/gear/gear[1]/wow");
wow += getprop("/gear/gear[2]/wow");
if ( agl > 25.0 and wow == 0 ) {
# airborne
# gear up
setprop("/controls/gear/gear-down", 0);
setprop("/uas/state", "launch-climbout");
print("state -> launch-climbout");
# target speed 250 kts
setprop("/autopilot/locks/speed", "speed-with-throttle");
setprop("/autopilot/settings/target-speed-kt", climbout_speed_kt);
# turn off rudder heading hold
setprop("/autopilot/locks/yaw", "");
setprop("/controls/flight/rudder", 0.0);
}
} elsif ( state == "launch-takeoff-winch" ) {
# keep throttle off until we release the winch line
throttle = 0.0;
setprop("/controls/engines/engine[0]/throttle", throttle);
setprop("/controls/engines/engine[1]/throttle", throttle);
setprop("/controls/engines/engine[2]/throttle", throttle);
setprop("/controls/engines/engine[3]/throttle", throttle);
# release parking brake
setprop("/controls/gear/brake-parking", 0);
var winch_open = getprop("/sim/hitches/winch/open");
if ( winch_open == 0 ) {
var winch_speed = getprop("/sim/hitches/winch/winch/rel-speed");
winch_speed += dt * 0.2;
if ( winch_speed > 0.6 ) {
winch_speed = 0.6;
}
setprop("/sim/hitches/winch/winch/rel-speed", winch_speed);
}
var airspeed = getprop("/velocities/airspeed-kt");
var groundspeed = getprop("/velocities/groundspeed-kt");
if ( groundspeed > 35.0 ) {
# assume we are pretty much flying by now if our ground
# speed is over 40
# gear up
setprop("/controls/gear/gear-down", 0);
setprop("/sim/hitches/winch/open", 1);
setprop("/uas/state", "launch-climbout");
print("state -> launch-climbout");
# target speed 250 kts
setprop("/autopilot/locks/speed", "elevator");
setprop("/autopilot/settings/target-speed-kt", climbout_speed_kt);
# turn off rudder heading hold
setprop("/autopilot/locks/yaw", "");
setprop("/controls/flight/rudder", 0.0);
}
} elsif ( state == "launch-climbout" ) {
var agl = getprop("/position/altitude-agl-ft");
var alt = getprop("/position/altitude-ft");
#var current_pitch_target
# = getprop("/autopilot/settings/target-pitch-deg");
#if ( current_pitch_target < 9.9 ) {
# current_pitch_target += 8*dt;
#} elsif ( current_pitch_target > 10.1 ) {
# current_pitch_target -= 2*dt;
#} else {
# current_pitch_target = 10;
#}
#setprop("/autopilot/locks/altitude", "pitch-hold");
#setprop("/autopilot/settings/target-pitch-deg", current_pitch_target);
if ( agl > climbout_alt_ft ) {
# flaps up, set target altitude, activate route manager
var target_alt = int((alt + operational_alt) / 100) * 100;
setprop("/controls/flight/flaps", 0);
setprop("/controls/flight/flapscommand", 0);
var routesize = getprop("/autopilot/route-manager/route/num");
if ( routesize > 0 ) {
#setprop("/autopilot/route-manager/active", 1);
setprop("/autopilot/route-manager/current-wp", 0);
}
setprop("/autopilot/locks/heading", "true-heading-hold");
# if next task is to fly the route and we are flying pitch
# angle on climbout, otherwise, comment these lines out
#setprop("/autopilot/settings/target-pitch-deg", 2);
#setprop("/autopilot/settings/target-altitude-ft", target_alt);
#setprop("/autopilot/locks/altitude", "altitude-hold");
var routesize = getprop("/autopilot/route-manager/route/num");
if ( routesize > 0 ) {
setprop("/uas/state", "route");
print("state -> route");
setprop("/uas/camera-target", "Next Waypoint");
} else {
setprop("/uas/state", "circle");
print("state -> circle");
if ( mode == "carrier" ) {
setprop("/uas/camera-target", "Carrier");
} else {
setprop("/uas/camera-target", "Operator");
}
}
update_camera_target();
}
} elsif ( state == "refuel" and enable_tanker ) {
var fuel_probe_horiz = 0; # (m) positive moves the airplane left relative to the tanker
var fuel_probe_vert = 0; # (ft) postiive moves the airplane up relative to the tanker
var fuel_probe_trail = 0; # (m)
#print("tanker hdg = ", tanker_heading, " spd = ", tanker_speed );
var tanker_bearing = getprop("/ai/models/tanker/radar/bearing-deg");
var tanker_dist = getprop("/ai/models/tanker/radar/range-nm");
var tanker_alt = getprop("/ai/models/tanker/position/altitude-ft");
var tanker_lon = getprop("/ai/models/tanker/position/longitude-deg");
var tanker_lat = getprop("/ai/models/tanker/position/latitude-deg");
set_runway_location_by_coord( tanker_lon, tanker_lat,
tanker_alt + fuel_probe_vert,
tanker_heading );
calc_xtrack();
var own_alt = getprop("/position/altitude-ft");
# override xtrack() calcs
var course_from = tanker_bearing - 180.0;
if ( course_from < 0 ) { course_from += 360.0; }
var deg_offset = course_from - tanker_heading;
if ( deg_offset < -180 ) { deg_offset += 360.0; }
runway_distance_to = tanker_dist * NM2M;
xtrack = math.sin( deg_offset * D2R ) * runway_distance_to;
rwy_dist = math.cos( deg_offset * D2R ) * runway_distance_to;
setprop("/uas/xtrack", xtrack);
setprop("/uas/rwy-dist", rwy_dist);
if ( refuel_state == 0 ) {
# enter holding position
fuel_probe_horiz = 100;
fuel_probe_vert = -30;
fuel_probe_trail = 200;
if ( math.abs(rwy_dist) < (fuel_probe_trail * 1.1) and
math.abs(own_alt - tanker_alt) < 50 ) {
refuel_state = 1;
}
} elsif ( refuel_state == 1 ) {
# slide over into position
fuel_probe_horiz = 1.5;
fuel_probe_vert = -9;
fuel_probe_trail = 35;
if ( math.abs( fuel_probe_horiz + xtrack ) < 1.0 ) {
timer_start = time;
f14.refuel_probe_switch_cycle();
refuel_state = 2;
}
} elsif ( refuel_state == 2 ) {
# move to final refueling position
fuel_probe_horiz = 1.5;
fuel_probe_vert = -9;
fuel_probe_trail = 30;
if ( time > timer_start + 45 ) {
timer_start = time;
refuel_state = 3;
}
} elsif ( refuel_state == 3 ) {
# don't waste too much time dropping off, lets just bug
# out after 15 seconds to keep life exciting
fuel_probe_horiz = 5;
fuel_probe_vert = -14;
fuel_probe_trail = 100;
if ( time > timer_start + 15 ) {
f14.refuel_probe_switch_cycle();
f14.refuel_probe_switch_cycle();
setprop("/uas/state", "gohome");
print("state -> gohome");
}
}
var own_indspeed
= getprop("/instrumentation/airspeed-indicator/indicated-speed-kt");
var speed_ratio = 1;
if ( own_groundspeed > 0 ) {
speed_ratio = own_indspeed / own_groundspeed;
}
var speed_offset = 0;
var target_speed = 0;
if ( rwy_dist < -1000 and rwy_distance_to > 4000 ) {
# "well" behind the tanker and further than 4000m away, fly direct to
var heading = rwy_course_to - 180;
if ( heading < 0 ) { heading += 360.0; }
setprop("/autopilot/settings/true-heading-deg", heading);
# speed offset based on total distance from
speed_offset = (rwy_distance_to - fuel_probe_trail)*M2NM * 200;
target_speed = tanker_speed * speed_ratio + speed_offset;
} else {
# compute target heading to fly the runway centerline with
# wind compensation
var gain = 0.0499 * (550 - own_indspeed) / 300 + 0.0001;
if ( gain < 0.0001 ) { gain = 0.0001; }
if ( gain > 0.05 ) { gain = 0.05; }
var xtrack_comp = (xtrack + fuel_probe_horiz) * gain;
if ( xtrack_comp < -45 ) { xtrack_comp = -45; }
if ( xtrack_comp > 45 ) { xtrack_comp = 45; }
var target_crs = (tanker_heading - xtrack_comp);
var truespeed
= getprop("/instrumentation/airspeed-indicator/true-speed-kt");
var windtri = wind_course( target_crs, truespeed, 0 );
# account for the fact that we might not be flying the same
# way we are pointing. In real life accurate beta is hard to
# come by so maybe at some point I need to think of a better
# way (involving ground track heading perhaps) to zero out
# this potential bias.
var beta = getprop("/orientation/side-slip-deg");
windtri.heading += beta;
if ( windtri.heading < 0.0 ) { windtri.heading += 360.0; }
if ( windtri.heading >= 360.0 ) { windtri.heading -= 360.0; }
setprop("/autopilot/settings/true-heading-deg", windtri.heading);
# speed offset based on distance along tanker path
var dist_offset = -rwy_dist - fuel_probe_trail;
speed_offset = dist_offset * M2NM * 200;
if ( dist_offset < 0 ) {
# leading (back off speed more quickly)
target_speed = tanker_speed * speed_ratio + speed_offset * 20;
} else {
# trailing (need to catch up)
target_speed = tanker_speed * speed_ratio + speed_offset;
}
}
if ( target_speed < 200 ) { target_speed = 200; }
if ( target_speed > max_speed ) { target_speed = max_speed; }
setprop("/autopilot/settings/target-speed-kt", target_speed);
manage_speedbrake( target_speed, 0 );
var alt_diff = tanker_alt - own_alt;
if ( alt_diff > 2000 ) {
var current_pitch_target
= getprop("/autopilot/settings/target-pitch-deg");
if ( current_pitch_target < 14.9 ) {
current_pitch_target += dt;
} elsif ( current_pitch_target > 15.1 ) {
current_pitch_target -= dt;
} else {
current_pitch_target = 15;
}
setprop("/autopilot/locks/altitude", "pitch-hold");
setprop("/autopilot/settings/target-pitch-deg",
current_pitch_target);
} elsif ( alt_diff > 1000 ) {
var pitch = 10 + (alt_diff - 1000) / 200;
setprop("/autopilot/locks/altitude", "pitch-hold");
setprop("/autopilot/settings/target-pitch-deg", pitch);
} elsif ( alt_diff > 500 ) {
var pitch = 5 + (alt_diff - 500) / 100;
setprop("/autopilot/locks/altitude", "pitch-hold");
setprop("/autopilot/settings/target-pitch-deg", pitch);
} else {
setprop("/autopilot/locks/altitude", "altitude-hold");
setprop("/autopilot/settings/target-altitude-ft",
tanker_alt + fuel_probe_vert);
}
} elsif ( state == "route" ) {
var wp_capture_dist_m = 50.0;
var target_alt = getprop("/uas/flight-altitude-ft");
setprop("/autopilot/settings/target-altitude-ft", target_alt);
var target_speed = route_speed;
setprop("/autopilot/settings/target-speed-kt", target_speed);
setprop("/autopilot/locks/altitude", "throttle");
var curwp = getprop("/autopilot/route-manager/current-wp");
var routesize = getprop("/autopilot/route-manager/route/num");
# fetch previous waypoint
var wp_prev = geo.aircraft_position(); # temp init
if ( curwp > 0 ) {
var prevwp = curwp - 1;
var wp_path = "/autopilot/route-manager/route/wp[" ~ prevwp ~ "]";
var wp_node = props.globals.getNode(wp_path);
if ( wp_node != nil ) {
wp_prev.set_lon( wp_node.getChild("longitude-deg").getValue());
wp_prev.set_lat( wp_node.getChild("latitude-deg").getValue());
}
}
# fetch current waypoint
var wp_cur = geo.aircraft_position(); # temp init
var wp_path = "/autopilot/route-manager/route/wp[" ~ curwp ~ "]";
var wp_node = props.globals.getNode(wp_path);
if ( wp_node != nil ) {
wp_cur.set_lon( wp_node.getChild("longitude-deg").getValue());
wp_cur.set_lat( wp_node.getChild("latitude-deg").getValue());
}
var course_to = 0.0;
var dist_m = 0.0;
cur_coord = geo.aircraft_position();
if ( curwp > 0 ) {
var leg_course = wp_prev.course_to( wp_cur );
var leg_dist = wp_prev.distance_to( wp_cur );
var direct_course = cur_coord.course_to( wp_cur );
var direct_dist = cur_coord.distance_to( wp_cur );
var angle = leg_course - direct_course;
if ( angle < -180 ) {
angle += 360.0;
} elsif ( angle > 180 ) {
angle -= 360.0;
}
var xtrack = math.sin( angle * D2R ) * direct_dist;
dist_m = math.cos( angle * D2R ) * direct_dist;
setprop("/uas/xtrack", xtrack);
setprop("/uas/track-dist", dist_m);
var xtrack_comp = xtrack * xtrack_route_gain;
if ( xtrack_comp < -45 ) { xtrack_comp = -45; }
if ( xtrack_comp > 45 ) { xtrack_comp = 45; }
course_to = leg_course - xtrack_comp;
if ( course_to < 0.0 ) {
course_to += 360.0;
} elsif ( course_to > 360.0 ) {
course_to -= 360.0;
}
} else {
course_to = cur_coord.course_to( wp_cur );
dist_m = cur_coord.distance_to( wp_cur );
setprop("/uas/xtrack", 0.0);
setprop("/uas/track-dist", dist_m);
}
#printf("course_to = %.1f dist_m = %.0f\n", course_to, dist_m);
# advance to next waypoint if 'close enough'
if ( dist_m <= wp_capture_dist_m ) {
if ( curwp < routesize - 1 ) {
curwp += 1;
setprop("/autopilot/route-manager/current-wp", curwp);
} else {
# route finished, circle last waypoint untilcommanded otherwise.
# disable route manager (if active)
# setprop("/autopilot/route-manager/active", 0);
setprop("/uas/state", "circle");
print("state -> circle");
setprop("/uas/camera-target", "Operator");
update_camera_target();
}
}
var truespeed =
getprop("/instrumentation/airspeed-indicator/true-speed-kt");
var windtri = wind_course( course_to, truespeed, 0 );
setprop("/autopilot/settings/true-heading-deg", windtri.heading);
} elsif ( state == "circle" ) {
var target_alt = getprop("/uas/flight-altitude-ft");
setprop("/autopilot/settings/target-altitude-ft", target_alt);
circle_coord.set_lon( getprop("/sim/input/click/longitude-deg") );
circle_coord.set_lat( getprop("/sim/input/click/latitude-deg") );
circle_coord.set_alt( getprop("/sim/input/click/elevation-ft")*FT2M );
var cur_coord = geo.aircraft_position();
var course_to = cur_coord.course_to( circle_coord );
var dist_to = cur_coord.distance_to( circle_coord );
#printf("course_to = %.1f dist_to = %.0f\n", course_to, dist_to);
# compute ideal heading if at ideal radius
var ideal_hdg = course_to + 90;
if ( ideal_hdg > 360.0 ) { ideal_hdg -= 360.0; }
# compute idea radius for a "circle_bank" degree bank
var radius_ft = (circle_speed * circle_speed)
/ (11.23*math.tan(0.01745 * circle_bank));
var radius_m = radius_ft * FT2M;
# adjust target heading for our actual radius
var target_hdg = ideal_hdg;
if ( dist_to < radius_m ) {
# inside circle, adjust target heading to expand our
# circling radius
var offset_deg = 90 * (1.0 - dist_to / radius_m);
target_hdg += offset_deg;
if ( target_hdg > 360.0 ) { target_hdg -= 360.0; }
} elsif ( dist_to > radius_m ) {
# outside circle, adjust target heading to tighten our
# circling radius
var offset_dist = dist_to - radius_m;
if ( offset_dist > radius_m ) { offset_dist = radius_m; }
var offset_deg = 90 * offset_dist / radius_m;
target_hdg -= offset_deg;
if ( target_hdg < 0.0 ) { target_hdg += 360.0; }
}
var truespeed =
getprop("/instrumentation/airspeed-indicator/true-speed-kt");
var windtri = wind_course( target_hdg, truespeed, 0 );
setprop("/autopilot/settings/true-heading-deg", windtri.heading);
setprop("/autopilot/settings/target-speed-kt", circle_speed);
manage_speedbrake( circle_speed, 5 );
var airspeed = getprop("/velocities/airspeed-kt");
if ( airspeed <= flap_speed_kt ) {
setprop("/controls/flight/flaps", 1);
setprop("/controls/flight/flapscommand", 1);
} else if ( airspeed > flap_speed_kt + 10 ) {
setprop("/controls/flight/flaps", 0);
setprop("/controls/flight/flapscommand", 0);
}
} elsif ( state == "heading" ) {
var target_alt = getprop("/uas/flight-altitude-ft");
setprop("/autopilot/settings/target-altitude-ft", target_alt);
# adjust target heading for our actual radius
var target_hdg = getprop("/uas/wmi/teleop-hdg-deg");
var truespeed =
getprop("/instrumentation/airspeed-indicator/true-speed-kt");
var windtri = wind_course( target_hdg, truespeed, 0 );
setprop("/autopilot/settings/true-heading-deg", windtri.heading);
setprop("/autopilot/settings/target-speed-kt", max_speed);
manage_speedbrake( max_speed, 5 );
var airspeed = getprop("/velocities/airspeed-kt");
if ( airspeed <= flap_speed_kt ) {
setprop("/controls/flight/flaps", 1);
setprop("/controls/flight/flapscommand", 1);
} else if ( airspeed > flap_speed_kt + 10 ) {
setprop("/controls/flight/flaps", 0);
setprop("/controls/flight/flapscommand", 0);
}
} elsif ( state == "gohome" ) {
if ( rwy_distance_to > 2000 ) {
# fly direct to
var alt_offset = math.sin(glideslope*D2R) * rwy_distance_to;
var target_alt = (rwy_coord.alt() + alt_offset) * M2FT;
if ( target_alt < rwy_coord.alt()*M2FT + pattern_height_ft ) {
target_alt = rwy_coord.alt()*M2FT + pattern_height_ft;
}
setprop("/autopilot/locks/altitude", "throttle");
setprop("/autopilot/settings/target-altitude-ft", target_alt);
# optimal speed is a forward speed that allows us to
# arrive at the destination about the same time that we
# decend to the correct altitude.
var alt = getprop("/position/altitude-ft");
var alt_diff = alt - pattern_height_ft;
var secs_to_alt = 60 * alt_diff / 3000; # 3k fpm decent
var mps = rwy_distance_to / secs_to_alt;
var optimal_kts = mps * MPS2KT;
var heading = rwy_course_to - 180;
if ( heading < 0 ) { heading += 360.0; }
var truespeed
= getprop("/instrumentation/airspeed-indicator/true-speed-kt");
var windtri = wind_course( heading, truespeed, 0 );
setprop("/autopilot/locks/heading", "true-heading-hold");
setprop("/autopilot/settings/true-heading-deg", windtri.heading);
var heading_error
= getprop("/autopilot/internal/true-heading-error-deg");
var target_speed = 0;
if ( math.abs(heading_error) > 60 ) {
# if we are turned in the wrong direction, keep our
# speed low until we get aimed more towards home.
target_speed = downwind_speed_kt;
} else {
target_speed = optimal_kts;
# speed limit based on total distance from rwy so we
# can arrive in the pattern at pattern speed
speed_offset = (rwy_distance_to - 2000) / 40;
speed_limit = downwind_speed_kt + speed_offset;
if ( target_speed > speed_limit ) {
target_speed = speed_limit;
}
if ( target_speed < downwind_speed_kt ) {
target_speed = downwind_speed_kt;
}
if ( target_speed > max_speed ) { target_speed = max_speed; }
}
setprop("/autopilot/settings/target-speed-kt", target_speed);
manage_speedbrake( target_speed, 5 );
var airspeed = getprop("/velocities/airspeed-kt");
if ( airspeed <= flap_speed_kt ) {
setprop("/controls/flight/flaps", 1);
setprop("/controls/flight/flapscommand", 1);
} else if ( airspeed > flap_speed_kt + 10 ) {
setprop("/controls/flight/flaps", 0);
setprop("/controls/flight/flapscommand", 0);
}
} else {
setprop("/uas/camera-target", "Carrier");
setprop("/autopilot/locks/heading", "true-heading-hold");
update_camera_target();
approach_dw_alt = getprop("/position/altitude-ft");
setprop("/uas/state", "downwind");
print("state -> downwind");
}
} elsif ( state == "downwind" ) {
# fly downwind to setup for rwy_gate entrance
# currently hardwired for a "lefthand" approach
setprop("/autopilot/settings/target-speed-kt", downwind_speed_kt);
manage_speedbrake( downwind_speed_kt, 5 );
var cur_alt = getprop("/position/altitude-ft");
compute_approach_metrics( dt );
# lock on glideslope when close enough
if ( approach_alt_error < 0.0 ) {
approach_lock_glideslope = 1;
}
setprop("/uas/approach/gs-lock", approach_lock_glideslope);
if ( approach_lock_glideslope ) {
# fly decent rate based altitude profile
var alt_bias = 25; # fly the downwind leg this much higher
# than the computed ideal so we don't
# end up too low on the base/final leg.
# offset target decent rate to get on the glide slope
var offset_rate = ((approach_alt_error + alt_bias)*M2FT / 60) * 5;
approach_vertspeed_fps += offset_rate;
# cap at +appoach_max_climb_rate/-1000 fpm off from ideal
if ( approach_vertspeed_fps > approach_max_climb_rate ) {
approach_vertspeed_fps = approach_max_climb_rate;
}
if ( approach_vertspeed_fps < approach_max_decent_rate ) {
approach_vertspeed_fps = approach_max_decent_rate;
}
} else {
# maintain current altitude until glideslope locked, climb
# only if below pattern altitude
var target_alt = approach_dw_alt;
var pattern_alt = rwy_coord.alt()*M2FT + pattern_height_ft;
if ( target_alt < pattern_alt ) {
target_alt = pattern_alt;
}
var alt_error = target_alt - cur_alt;
# offset target decent rate to get on the glide slope
var offset_rate = ((alt_error*M2FT) / 60) * 5;
approach_vertspeed_fps += offset_rate;
# cap at +appoach_max_climb_rate/-1000 fpm off from ideal
if ( approach_vertspeed_fps > approach_max_climb_rate ) {
approach_vertspeed_fps = approach_max_climb_rate;
}
if ( approach_vertspeed_fps < approach_max_decent_rate ) {
approach_vertspeed_fps = approach_max_decent_rate;
}
}
setprop("/uas/approach/target-vert-speed", approach_vertspeed_fps);
setprop("/autopilot/locks/altitude", "vertical-speed-hold");
setprop("/autopilot/settings/target-rate-of-climb",
approach_vertspeed_fps);
# desired xtrack error for outbound leg is the turning
# diameter (+ fudge factor). Note: xtrack is negative for a
# left handed approach
# compute target heading to fly the outbound offset runway
# centerline with wind compensation
var error = approach_diameter + xtrack;
var xtrack_comp = error * xtrack_downwind_gain;
if ( xtrack_comp < -60 ) { xtrack_comp = -60; }
if ( xtrack_comp > 60 ) { xtrack_comp = 60; }
var target_crs = (rwy_recip + xtrack_comp);
var windtri = {};
var truespeed
= getprop("/instrumentation/airspeed-indicator/true-speed-kt");
windtri = wind_course( target_crs, truespeed, (mode == "carrier") );
setprop("/autopilot/settings/true-heading-deg", windtri.heading);
var heading_error
= getprop("/autopilot/internal/true-heading-error-deg");
var airspeed = getprop("/velocities/airspeed-kt");
if ( airspeed <= flap_speed_kt ) {
setprop("/controls/flight/flaps", 1);
setprop("/controls/flight/flapscommand", 1);
}
# detect ready to turn base (slow enough, close enough to
# xtrack target, close enough to outbound heading, and past
# the 45 turn point and not above the ideal glide slope
# altitude)
if ( airspeed < downwind_speed_kt + 10
and math.abs(error) < (approach_diameter * 0.25)
and math.abs(heading_error) < 15
and -rwy_dist > approach_45_dist
and approach_alt_error > -5 )
{
var target_heading = getprop("/autopilot/settings/true-heading-deg");
target_heading -= 90; # 90 degree left turn
setprop("/autopilot/settings/true-heading-deg", target_heading);
setprop("/autopilot/settings/target-speed-kt", final_speed_kt);
setprop("/controls/flight/flaps", 1);
setprop("/controls/flight/flapscommand", 1);
setprop("/controls/flight/ground-spoilers-armed", 1);
setprop("/uas/state", "final");
print("state -> final");
}
} elsif ( state == "base" ) {
# fly base leg to setup for rwy_gate entrance
# currently hardwired for a "lefthand" approach
# compute angle of remainder of turn to rwy heading
var current_heading = getprop("/orientation/heading-deg");
var hdg_diff = rwy_heading - current_heading;
if ( hdg_diff < -180 ) { hdg_diff += 360.0; }
if ( hdg_diff > 180 ) { hdg_diff -= 360.0; }
# percent of 180 deg turn
var turn_frac = math.abs(hdg_diff) / 180.0;
# compute turning diameter for current speed
var airspeed = getprop("/velocities/airspeed-kt");
var radius_ft = (airspeed*airspeed) / (11.23*math.tan(0.01745*max_pattern_bank));
var radius_m = radius_ft * FT2M;
compute_approach_metrics( dt );
var alt = ( rwy_coord.alt()
+ math.tan(glideslope*D2R) * approach_dist_to_touchdown ) * M2FT;
if ( alt > rwy_coord.alt()*M2FT + pattern_height_ft ) {
alt = rwy_coord.alt()*M2FT + pattern_height_ft;
}
if ( alt < rwy_gate.alt()*M2FT ) {
alt = rwy_gate.alt()*M2FT;
}
setprop("/autopilot/settings/target-altitude-ft", alt);
# detect ready to turn final (close enough to base heading)
var heading_error = getprop("/autopilot/internal/true-heading-error-deg");
# within 135 degrees of final rwy heading, start thinking
# about the turn to final
if ( turn_frac < ( 6.0 / 8.0 ) ) {
setprop("/autopilot/settings/target-speed-kt", final_speed_kt);
setprop("/controls/flight/flaps", 1);
setprop("/controls/flight/flapscommand", 1);
setprop("/controls/flight/ground-spoilers-armed", 1);
closing_speed_kt = 0.0;
setprop("/uas/state", "final");
print("state -> final");
}
} elsif ( state == "final" ) {
# lower gear when airspeed < gear_down_speed_kt
var airspeed = getprop("/velocities/airspeed-kt");
if ( airspeed <= gear_down_speed_kt ) {
setprop("/controls/gear/gear-down", 1);
}
if ( mode == "carrier" ) {
if ( -rwy_dist < 4000.0 ) {
setprop("/controls/gear/tailhook", 1);
# make sure gear is dropped now if it aint already,
# cause we'll need it real soon!
setprop("/controls/gear/gear-down", 1);
}
}
var heading_error
= getprop("/autopilot/internal/true-heading-error-deg");
var cur_alt = getprop("/position/altitude-ft");
compute_approach_metrics( dt );
# lock on glideslope when close enough
if ( approach_alt_error < 0.0 ) {
approach_lock_glideslope = 1;
}
setprop("/uas/approach/gs-lock", approach_lock_glideslope);
if ( approach_lock_glideslope ) {
# fly decent rate based altitude profile
# offset target decent rate to get on the glide slope
var offset_rate = (approach_alt_error*M2FT / 60) * 5;
approach_vertspeed_fps += offset_rate * 1.5;
# cap at +appoach_max_climb_rate/-1000 fpm off from ideal
if ( approach_vertspeed_fps > approach_max_climb_rate ) {
approach_vertspeed_fps = approach_max_climb_rate;
}
if ( approach_vertspeed_fps < approach_max_decent_rate ) {
approach_vertspeed_fps = approach_max_decent_rate;
}
} else {
# maintain current altitude until glideslope locked, climb
# only if below pattern altitude
var target_alt = approach_dw_alt;
var pattern_alt = rwy_coord.alt()*M2FT + pattern_height_ft;
if ( target_alt < pattern_alt ) {
target_alt = pattern_alt;
}
var alt_error = target_alt - cur_alt;
# offset target decent rate to get on the glide slope
var offset_rate = ((alt_error*M2FT) / 60) * 5;
approach_vertspeed_fps += offset_rate;
# cap at +appoach_max_climb_rate/-1000 fpm off from ideal
if ( approach_vertspeed_fps > approach_max_climb_rate ) {
approach_vertspeed_fps = approach_max_climb_rate;
}
if ( approach_vertspeed_fps < approach_max_decent_rate ) {
approach_vertspeed_fps = approach_max_decent_rate;
}
}
setprop("/uas/approach/target-vert-speed", approach_vertspeed_fps);
setprop("/autopilot/locks/altitude", "vertical-speed-hold");
setprop("/autopilot/settings/target-rate-of-climb",
approach_vertspeed_fps);
# compute target heading to fly the runway centerline with
# wind compensation
var xtrack_comp = xtrack * xtrack_final_gain;
if ( xtrack_comp < -45 ) { xtrack_comp = -45; }
if ( xtrack_comp > 45 ) { xtrack_comp = 45; }
var target_crs = (rwy_heading - xtrack_comp);
var windtri = {};
var truespeed
= getprop("/instrumentation/airspeed-indicator/true-speed-kt");
windtri = wind_course( target_crs, truespeed, (mode == "carrier") );
#print("true-heading-deg=", windtri.heading);
# account for the fact that we might not be flying the same
# way we are pointing. In real life accurate beta is hard to
# come by so maybe at some point I need to think of a better
# way (involving ground track heading perhaps) to zero out
# this potential bias.
var beta = getprop("/orientation/side-slip-deg");
windtri.heading += beta;
if ( windtri.heading < 0.0 ) { windtri.heading += 360.0; }
if ( windtri.heading >= 360.0 ) { windtri.heading -= 360.0; }
setprop("/autopilot/settings/true-heading-deg", windtri.heading);
var agl = getprop("/position/altitude-agl-ft");
if ( agl < -approach_vertspeed_fps * flare_gain ) {
setprop("/uas/state", "flare");
print("state -> flare");
}
} elsif ( state == "flare" ) {
# turn off autothrottle, and cut engines
setprop("/autopilot/locks/speed", "");
if ( mode == "runway" ) {
# try to level out
var agl = getprop("/position/altitude-agl-ft");
setprop("/autopilot/settings/target-rate-of-climb",
-agl / flare_gain );
setprop("/controls/engines/engine[0]/throttle", 0.0);
setprop("/controls/engines/engine[1]/throttle", 0.0);
setprop("/controls/engines/engine[2]/throttle", 0.0);
setprop("/controls/engines/engine[3]/throttle", 0.0);
}
var wow = 0;
wow += getprop("/gear/gear[0]/wow");
wow += getprop("/gear/gear[1]/wow");
wow += getprop("/gear/gear[2]/wow");
if ( wow > 0 ) {
setprop("/uas/state", "touchdown");
print("state -> touchdown");
}
} elsif ( state == "touchdown" ) {
# apply full brakes
setprop("/controls/gear/brake-right", 1.0);
setprop("/controls/gear/brake-left", 1.0);
if ( mode == "carrier" ) {
# go full throttle in case we missed the cable
setprop("/controls/engines/engine[0]/throttle", 0.759);
setprop("/controls/engines/engine[1]/throttle", 0.759);
setprop("/controls/engines/engine[2]/throttle", 0.759);
setprop("/controls/engines/engine[3]/throttle", 0.759);
var airspeed = getprop("/velocities/airspeed-kt");
var wow = 0;
wow += getprop("/gear/gear[0]/wow");
wow += getprop("/gear/gear[1]/wow");
wow += getprop("/gear/gear[2]/wow");
if ( wow > 0 and airspeed < 100 ) {
# caught the wire, shut down the engines
setprop("/controls/engines/engine[0]/throttle", 0.0);
setprop("/controls/engines/engine[1]/throttle", 0.0);
setprop("/controls/engines/engine[2]/throttle", 0.0);
setprop("/controls/engines/engine[3]/throttle", 0.0);
setprop("/uas/state", "shutdown");
print("state -> shutdown");
}
}
# flaps up to kill lift
# setprop("/controls/flight/flaps", 0.0);
} elsif ( state == "shutdown" ) {
var groundspeed = getprop("/velocities/groundspeed-kt");
if ( mode == "runway" ) {
if ( groundspeed < 10 ) {
setprop("/controls/flight/ground-spoilers-armed", 0);
setprop("/uas/state", "end");
print("state -> end");
}
} elsif ( mode == "carrier" ) {
if ( groundspeed < carrier_speed + 10 ) {
setprop("/controls/flight/ground-spoilers-armed", 0);
setprop("/uas/state", "end");
print("state -> end");
}
}
}
}
var main_loop = func {
time = getprop("/sim/time/elapsed-sec");
var dt = time - last_time;
last_time = time;
if ( getprop("/uas/master-switch") == 1 ) {
#print("drone update start:", dt);
update_state( dt );
#print("drone update end.");
}
settimer(main_loop, 0);
}
setlistener("/sim/signals/fdm-initialized",
func {
# master switch starts off
props.globals.initNode("/uas/master-switch", 0, "BOOL", 1);
# set some defaults
setprop("/sim/hud/visibility[1]", 1); # hud on by default
setprop("/uas/view-mode", "Cockpit");
setprop("/uas/camera-zoom", "1.0");
setprop("/uas/emulate-pitch-bobble", 0);
# initial state
setprop("/uas/state", "");
# go
main_loop();
});
setlistener("/uas/view-mode", update_view);