#
# TOWING NASAL CODE
#
# original version by D-NXKT up to version 30.12.2014
# updates by Benedikt Wolf and D-NXKT, version 01/2023
#
#
# Purpose of this routine:
# ------------------------
#
# - Create visible winch- and towropes for gliders and towplanes
# - Support of aerotowing and winch for JSBSim-aircraft (glider and towplanes)
#
# This routine is very similar to /FDM/YASim/Hitch.cpp
# Aerotowing is fully compatible to the YASim functionality.
# This means that YASim-gliders could be towed by JSBSim-aircraft and vice versa.
# Setup-instructions with copy and paste examples are given below:
#
#
# Setup of visible winch/towropes for Yasim-aircraft:
# ----------------------------------------------------
#
# YASim-aircraft with winch/aerotowing functionality should work out of the box.
# Optional you can customize the rope-diameter by adding the following to "your_aircraft-set.xml":
#
#
#
#
# 10
#
#
#
#
# 20
#
#
#
#
#
# That's all!
#
#
#
# Support of aerotowing and winch for JSBSim-aircraft (glider and towplanes):
# ----------------------------------------------------------------------------
#
# 1. Define a hitch in the JSBSim-File. Coordinates according to JSBSims structural frame of reference
# (x points to the tail, y points to the right wing, z points upwards).
# Unit must be "LBS", frame must be "BODY". The force name is arbitrary.
#
#
#
#
# 3.65
# 0.0
# -0.12
#
#
# 0.0
# 0.0
# 0.0
#
#
#
# 2. Define controls for aerotowing and winch.
# Add the following key bindings in "yourAircraft-set.xml":
#
#
#
#
# Ctrl-o
# Find aircraft for aerotow
#
# nasal
#
#
#
#
#
# o
# Lock aerotow-hook
#
# nasal
#
#
#
#
#
# O
# Open aerotow-hook
#
# nasal
#
#
#
#
#
# Ctrl-w
# Place Winch and hook in
#
# nasal
#
#
#
#
#
# w
# Start winch
#
# nasal
#
#
#
#
#
# W
# Open winch-hook
#
# nasal
#
#
#
#
#
#
#
# For towplanes only "key n=79" (Open aerotow-hook) is required!
# 3. Set mandatory properties:
#
#
#
# hitch
# false
# 0.0
#
#
#
# hitch
#
#
#
#
#
# "force_name_jsbsim" must be the external force name in JSBSim.
# "force-is-calculated-by-other" should be "false" for gliders and "true" for tow planes.
# "mp-auto-connect-period" is only needed for tow planes and should be "1".
#
# IMPORTANT:
# The hitch location is stored twice in the property tree (for tow force and for rope animation).
# This is necessary to keep the towrope animation compatible to YASim-aircraft.
# The hitch location for the tow force is stored in "fdm/jsbsim/external_reactions/hitch/location-x(yz)-in" and for the
# animated towrope in "sim/hitches/aerotow(winch)/local-pos-x(yz)".
# By default only values for the tow force location have to be defined. The values for the towrope location are set
# automatically (decoupled-force-and-rope-locations is "false" by default).
# It is feasible to use different locations for the force and rope. In order to do this, you have to set
# "decoupled-force-and-rope-locations" to "true" and provide values for "sim/hitches/aerotow(winch)/local-pos-x(yz)".
# Note that the frame of reference is different. Here the coordinates for the "YASim-System" are needed
# (x points to the nose, y points to the left wing, z points upwards).
# 4. Set optional properties:
#
# Only aircraft-specific properties should be set by the aircraft. All other options can be adjusted by the
# winch GUI dialog and are stored by this script itself (TODO)
#
# Aircraft-specific properties are
# * weak link (aerotow/winch)
# sim/hitches/aerotow/tow/break-fource [N]
# sim/hitches/winch/tow/break-force [N]
# * typical tow speed (winch)
# sim/hitches/winch/typical-tow-speed-kph [kph]
# * automatic release angle (winch)
# sim/hitches/winch/automatic-release-angle-deg [deg]
#
#
#
#
#
# 6000
#
#
#
# 70.
# 100
# 5000
#
# 10000
#
#
#
#
# That's it!
################################################## general info ############################################
#
# 3 different types of towplanes could exist: AI-plane, MP-plane without interaction, MP-plane with interaction.
# AI-planes are identified by the node "ai/models/aircraft/" or "ai/models/dragger".
# MP-planes (interactice/non-interactive) are identified by the existence of node "ai/models/multiplayer".
# Interactive MP-plane: variables in node "ai/models/multiplayer/sim/hitches/" are updated.
# Non-interactive MP-plane: variables are not updated (values are either not defined or have "wrong" values
# from a former owner of this node.
#
# The following properties are transmitted in multiplayer:
# "sim/hitches/aerotow/tow/elastic-constant"
# "sim/hitches/aerotow/tow/weight-per-m-kg-m"
# "sim/hitches/aerotow/tow/dist"
# "sim/hitches/aerotow/tow/connected-to-property-node"
# "sim/hitches/aerotow/tow/connected-to-ai-or-mp-callsign"
# "sim/hitches/aerotow/tow/break-force"
# "sim/hitches/aerotow/tow/end-force-x"
# "sim/hitches/aerotow/tow/end-force-y"
# "sim/hitches/aerotow/tow/end-force-z"
# "sim/hitches/aerotow/is-slave"
# "sim/hitches/aerotow/speed-in-tow-direction"
# "sim/hitches/aerotow/open", open);
# "sim/hitches/aerotow/local-pos-x"
# "sim/hitches/aerotow/local-pos-y"
# "sim/hitches/aerotow/local-pos-z"
#
##############################################################################################################
# ######################################################################################################################
# check, if towing support makes sense
# ######################################################################################################################
# Check if node "sim/hitches" is defined. If not, return!
if (props.globals.getNode("sim/hitches") == nil ) return;
print("towing is active!");
# ######################################################################################################################
# set defaults / initialize at startup
# ######################################################################################################################
# set defaults for properties that are NOT already defined
var fdm = getprop("/sim/flight-model") or "";
if ( !(fdm == "jsb" or fdm == "yasim") ){
print("Unsupported FDM for towing");
return;
}
var check_or_create = func ( name, value, type ) {
if ( getprop( name ) == nil ){
return props.globals.initNode(name, value, type);
} else {
return props.globals.getNode(name);
}
}
# Load stored settings
var config_file = getprop("/sim/fg-home") ~ "/Export/hitch-config.xml";
var load_prop = func( property, cnfg_p ){
if( cnfg_p != nil ){
property.setValue( cnfg_p.getValue() );
}
}
var config = {
aerotow: {
elastic_constant: props.globals.initNode("sim/hitches/aerotow/tow/elastic-constant", 9111.0, "DOUBLE"), # saved in config
weight_per_m_kg_m: props.globals.initNode("sim/hitches/aerotow/tow/weight-per-m-kg-m", 0.35, "DOUBLE"), # saved in config
length: props.globals.initNode("sim/hitches/aerotow/tow/length", 60.0, "DOUBLE"), # saved in config
path_to_model: props.globals.initNode("sim/hitches/aerotow/rope/path_to_model", "Models/Aircraft/towropes.xml", "STRING"), # saved in config
rope_diameter_mm: props.globals.initNode("sim/hitches/aerotow/rope/rope-diameter-mm", 20.0, "DOUBLE"), # saved in config
},
winch: {
elastic_constant: props.globals.initNode("sim/hitches/winch/tow/elastic-constant", 40001.0, "DOUBLE"), # saved in config
weight_per_m_kg_m: props.globals.initNode("sim/hitches/winch/tow/weight-per-m-kg-m", 0.1, "DOUBLE"), # saved in config
initial_tow_length: props.globals.initNode("sim/hitches/winch/winch/initial-tow-length-m", 1000.0, "DOUBLE"), # saved in config
path_to_model: props.globals.initNode("sim/hitches/winch/rope/path_to_model", "Models/Aircraft/towropes.xml", "STRING"), # saved in config
rope_diameter_mm: props.globals.initNode("sim/hitches/winch/rope/rope-diameter-mm", 20.0, "DOUBLE"), # saved in config
rope_breakage: {
height: props.globals.initNode("sim/hitches/winch/breakage/height", 100.0, "DOUBLE"), # saved in config
random: props.globals.initNode("sim/hitches/winch/breakage/random", 0, "BOOL"), # saved in config
},
loss_of_power: {
height: props.globals.initNode("sim/hitches/winch/loss-of-power/height", 100.0, "DOUBLE"), # saved in config
random: props.globals.initNode("sim/hitches/winch/loss-of-power/random", 0, "BOOL"), # saved in config
},
max_tow_length_m: props.globals.initNode("sim/hitches/winch/winch/max-tow-length-m", 1500.0, "DOUBLE"), # saved in config
max_spool_speed: props.globals.initNode("sim/hitches/winch/winch/max-spool-speed-m-s", 40.0, "DOUBLE"), # saved in config
force_acceleration: props.globals.initNode("sim/hitches/winch/winch/force-acceleration-N-s", 1000.0, "DOUBLE"), # saved in config
spool_acceleration: props.globals.initNode("sim/hitches/winch/winch/spool-acceleration-m-s-s", 8.0, "DOUBLE"), # saved in config
max_unspool_speed: props.globals.initNode("sim/hitches/winch/winch/max-unspool-speed-m-s", 40.0, "DOUBLE"), # saved in config
max_force: props.globals.initNode("sim/hitches/winch/winch/max-force-N", 10000.0, "DOUBLE"), # saved in config
max_power: props.globals.initNode("sim/hitches/winch/winch/max-power-kW", 123.0, "DOUBLE"), # saved in config
magic_constant: props.globals.initNode("sim/hitches/winch/winch/magic-constant", 500.0, "DOUBLE"), # saved in config
messages: {
launch_signaller: props.globals.initNode("/sim/hitches/winch/messages/launch-signaller", 1, "BOOL"), # saved in config
winch_driver: props.globals.initNode("/sim/hitches/winch/messages/winch-driver", 1, "BOOL"), # saved in config
pilot: props.globals.initNode("/sim/hitches/winch/messages/pilot", 1, "BOOL"), # saved in config
remote_ac: props.globals.initNode("/sim/hitches/winch/messages/remote-ac", 1, "BOOL"), # saved in config
},
},
};
var write_config = func {
var c = props.Node.new();
var a = c.addChild("aerotow");
var w = c.addChild("winch");
a.initNode("elastic-constant").setDoubleValue( config.aerotow.elastic_constant.getDoubleValue() );
a.initNode("weight-per-m-kg-m").setDoubleValue( config.aerotow.weight_per_m_kg_m.getDoubleValue() );
a.initNode("length").setDoubleValue( config.aerotow.length.getDoubleValue() );
a.initNode("path_to_model","","STRING").setValue( config.aerotow.path_to_model.getValue() );
a.initNode("rope-diameter-mm").setDoubleValue( config.aerotow.rope_diameter_mm.getDoubleValue() );
w.initNode("elastic-constant").setDoubleValue( config.winch.elastic_constant.getDoubleValue() );
w.initNode("weight-per-m-kg-m").setDoubleValue( config.winch.weight_per_m_kg_m.getDoubleValue() );
w.initNode("initial-tow-length-m").setDoubleValue( config.winch.initial_tow_length.getDoubleValue() );
w.initNode("path_to_model","","STRING").setValue( config.winch.path_to_model.getValue() );
w.initNode("rope-diameter-mm").setDoubleValue( config.winch.rope_diameter_mm.getDoubleValue() );
w.initNode("breakage/height").setDoubleValue(config.winch.rope_breakage.height.getDoubleValue() );
w.initNode("breakage/random",0,"BOOL").setBoolValue(config.winch.rope_breakage.random.getBoolValue() );
w.initNode("loss-of-power/height").setDoubleValue(config.winch.loss_of_power.height.getDoubleValue() );
w.initNode("loss-of-power/random",0,"BOOL").setBoolValue(config.winch.loss_of_power.random.getBoolValue() );
w.initNode("max-tow-length-m").setDoubleValue( config.winch.max_tow_length_m.getDoubleValue() );
w.initNode("max-spool-speed-m-s").setDoubleValue( config.winch.max_spool_speed.getDoubleValue() );
w.initNode("force-acceleration-N-s").setDoubleValue( config.winch.force_acceleration.getDoubleValue() );
w.initNode("spool-acceleration-m-s-s").setDoubleValue( config.winch.spool_acceleration.getDoubleValue() );
w.initNode("max-unspool-speed-m-s").setDoubleValue( config.winch.max_unspool_speed.getDoubleValue() );
w.initNode("max-force-N").setDoubleValue( config.winch.max_force.getDoubleValue() );
w.initNode("max-power-kW").setDoubleValue( config.winch.max_power.getDoubleValue() );
w.initNode("magic-constant").setDoubleValue( config.winch.magic_constant.getDoubleValue() );
w.initNode("messages/launch-signaller",1,"BOOL").setBoolValue( config.winch.messages.launch_signaller.getBoolValue() );
w.initNode("messages/winch-driver",1,"BOOL").setBoolValue( config.winch.messages.winch_driver.getBoolValue() );
w.initNode("messages/pilot",1,"BOOL").setBoolValue( config.winch.messages.pilot.getBoolValue() );
w.initNode("messages/remote-ac",1,"BOOL").setBoolValue( config.winch.messages.remote_ac.getBoolValue() );
io.write_properties( config_file, c );
}
if( io.stat( config_file ) != nil ){
var config_properties = io.read_properties( config_file );
load_prop( config.aerotow.elastic_constant, config_properties.getNode("aerotow/elastic-constant") );
load_prop( config.aerotow.weight_per_m_kg_m, config_properties.getNode("aerotow/weight-per-m-kg-m") );
load_prop( config.aerotow.length, config_properties.getNode("aerotow/length") );
load_prop( config.aerotow.path_to_model, config_properties.getNode("aerotow/path_to_model") );
load_prop( config.aerotow.rope_diameter_mm, config_properties.getNode("aerotow/rope-diameter-mm") );
load_prop( config.winch.elastic_constant, config_properties.getNode("winch/elastic-constant") );
load_prop( config.winch.weight_per_m_kg_m, config_properties.getNode("winch/weight-per-m-kg-m") );
load_prop( config.winch.initial_tow_length, config_properties.getNode("winch/initial-tow-length-m") );
load_prop( config.winch.path_to_model, config_properties.getNode("winch/path_to_model") );
load_prop( config.winch.rope_diameter_mm, config_properties.getNode("winch/rope-diameter-mm") );
load_prop( config.winch.rope_breakage.height, config_properties.getNode("winch/breakage/height") );
load_prop( config.winch.rope_breakage.random, config_properties.getNode("winch/breakage/random") );
load_prop( config.winch.loss_of_power.height, config_properties.getNode("winch/loss-of-power/height") );
load_prop( config.winch.loss_of_power.random, config_properties.getNode("winch/loss-of-power/random") );
load_prop( config.winch.max_tow_length_m, config_properties.getNode("winch/max-tow-length-m") );
load_prop( config.winch.max_spool_speed, config_properties.getNode("winch/max-spool-speed-m-s") );
load_prop( config.winch.force_acceleration, config_properties.getNode("winch/force-acceleration-N-s") );
load_prop( config.winch.spool_acceleration, config_properties.getNode("winch/spool-acceleration-m-s-s") );
load_prop( config.winch.max_unspool_speed, config_properties.getNode("winch/max-unspool-speed-m-s") );
load_prop( config.winch.max_force, config_properties.getNode("winch/max-force-N") );
load_prop( config.winch.max_power, config_properties.getNode("winch/max-power-kW") );
load_prop( config.winch.magic_constant, config_properties.getNode("winch/magic-constant") );
load_prop( config.winch.messages.launch_signaller, config_properties.getNode("winch/messages/launch-signaller") );
load_prop( config.winch.messages.winch_driver, config_properties.getNode("winch/messages/winch-driver") );
load_prop( config.winch.messages.pilot, config_properties.getNode("winch/messages/pilot") );
load_prop( config.winch.messages.remote_ac, config_properties.getNode("winch/messages/remote-ac") );
}
var aircraft_settings = {
aerotow: {
force_calc_by_other: check_or_create("sim/hitches/aerotow/force-is-calculated-by-other", 0, "BOOL"), # aircraft-specific
is_slave: check_or_create("sim/hitches/aerotow/is-slave", 0, "BOOL"), # aircraft-specific
break_force: check_or_create("sim/hitches/aerotow/tow/break-force", 12345.0, "DOUBLE"), # aircraft-specific
local_pos: [
check_or_create("sim/hitches/aerotow/local-pos-x", 0.0, "DOUBLE"), # aircraft-specific
check_or_create("sim/hitches/aerotow/local-pos-y", 0.0, "DOUBLE"), # aircraft-specific
check_or_create("sim/hitches/aerotow/local-pos-z", 0.0, "DOUBLE"), # aircraft-specific
],
},
winch: {
typical_tow_speed: props.globals.getNode("sim/hitches/winch/typical-tow-speed-kph"),
typical_tow_force: props.globals.getNode("sim/hitches/winch/typical-tow-force-N"),
# Winch type: 0 = tow with constant force; 1 = tow with constant rope speed; aircraft-specific
type_p: check_or_create("sim/hitches/winch/type", 0, "INT"),
break_force: check_or_create("sim/hitches/winch/tow/break-force", 12345.0, "DOUBLE"), # aircraft-specific
local_pos: [
check_or_create("sim/hitches/winch/local-pos-x", 0.0, "DOUBLE"), # aircraft-specific
check_or_create("sim/hitches/winch/local-pos-y", 0.0, "DOUBLE"), # aircraft-specific
check_or_create("sim/hitches/winch/local-pos-z", 0.0, "DOUBLE"), # aircraft-specific
],
},
};
var check_aircraft_tow_settings = func{
if( aircraft_settings.winch.typical_tow_speed == nil ){
aircraft_settings.winch.typical_tow_speed = props.globals.initNode( "sim/hitches/winch/typical-tow-speed-kph" );
# Estimate typical tow speed from aircraft weight, this will inherently be a rough guess
# Estimation basis:
# type | typical tow speed | typical weight | reference
# hang glider | 50 kph | 100 kg | https://www.safa.asn.au/resources/Tow%20Manual%20v5.4.1.pdf
# Ka6 | 90 kph | 250 kg | http://www.smbc-eferding.at/wp-content/uploads/2014/01/Flughandbuch-Ka-6.pdf
# LS8 | 120 kph | 500 kg | https://www.sglenzburg.ch/org/public/Dokumente/SGL/10-AFM/fhb_ls8a_rev3_tm8020.pdf
# DG-1000S | 130 kph | 650 kg | http://adelaidesoaring.on.net/wp-content/uploads/2014/01/DG-1000s-Flight-Manual.pdf
#
# a quadratic regression for this data leads to y = -0.0002x2 + 0.3286x + 20.839, we use (simplified): f(x) = -0.0002 * x^2 + 0.33 * x + 21
var weight_kg = weight_lb.getDoubleValue() * LB2KG;
aircraft_settings.winch.typical_tow_speed.setDoubleValue( -0.0002 * weight_kg * weight_kg + 0.33 * weight_kg + 21 );
}
if( aircraft_settings.winch.typical_tow_force == nil ){
aircraft_settings.winch.typical_tow_force = props.globals.initNode( "sim/hitches/winch/typical-tow-force-N" );
aircraft_settings.winch.typical_tow_force.setDoubleValue( 0.15 * math.pow( weight_lb.getDoubleValue(), 1.25 ) * LB2KG * 9.81);
}
}
# yasim properties for aerotow (should be already defined for yasim aircraft but not for JSBSim aircraft
var aerotow_hash = {
broken: props.globals.initNode("sim/hitches/aerotow/broken", 0, "BOOL"),
force: props.globals.initNode("sim/hitches/aerotow/force", 0.0, "DOUBLE"),
mp_auto_connect_period: props.globals.initNode("sim/hitches/aerotow/mp-auto-connect-period", 0.0, "DOUBLE"),
mp_time_lag: props.globals.initNode("sim/hitches/aerotow/mp-time-lag", 0.0, "DOUBLE"),
open: props.globals.initNode("sim/hitches/aerotow/open", 1, "BOOL"), #always init to true
speed_tow_direction: props.globals.initNode("sim/hitches/aerotow/speed-in-tow-direction", 0.0, "DOUBLE"),
old_open: props.globals.initNode("sim/hitches/aerotow/oldOpen", 1, "BOOL"),
tow: {
conn_ai_node: props.globals.initNode("sim/hitches/aerotow/tow/connected-to-ai-node", 0, "BOOL"),
conn_ai_or_mp_callsign: props.globals.initNode("sim/hitches/aerotow/tow/connected-to-ai-or-mp-callsign", "", "STRING"),
conn_ai_or_mp_id: props.globals.initNode("sim/hitches/aerotow/tow/connected-to-ai-or-mp-id", 0, "INT"),
conn_mp_node: props.globals.initNode("sim/hitches/aerotow/tow/connected-to-mp-node", 0, "BOOL"),
conn_prop_node: props.globals.initNode("sim/hitches/aerotow/tow/connected-to-property-node", 0, "BOOL"),
dist: props.globals.initNode("sim/hitches/aerotow/tow/dist", 0.0, "DOUBLE"),
end_force: [
props.globals.initNode("sim/hitches/aerotow/tow/end-force-x", 0.0, "DOUBLE"),
props.globals.initNode("sim/hitches/aerotow/tow/end-force-y", 0.0, "DOUBLE"),
props.globals.initNode("sim/hitches/aerotow/tow/end-force-z", 0.0, "DOUBLE"),
],
node: props.globals.initNode("sim/hitches/aerotow/tow/node", "", "STRING"),
length: props.globals.initNode("sim/hitches/aerotow/tow/length", 60.0, "DOUBLE"),
},
rope: {
exist: props.globals.initNode("sim/hitches/aerotow/rope/exist", 0, "BOOL"),
model_id: props.globals.initNode("sim/hitches/aerotow/rope/model_id", -1, "INT"),
lat: props.globals.initNode("ai/models/aerotowrope/position/latitude-deg", 0.0, "DOUBLE"),
lon: props.globals.initNode("ai/models/aerotowrope/position/longitude-deg", 0.0, "DOUBLE"),
alt: props.globals.initNode("ai/models/aerotowrope/position/altitude-ft", 0.0, "DOUBLE"),
hdg: props.globals.initNode("ai/models/aerotowrope/orientation/true-heading-deg", 0.0, "DOUBLE"),
pitch: props.globals.initNode("ai/models/aerotowrope/orientation/pitch-deg", 0.0, "DOUBLE"),
},
};
# yasim properties for winch (should already be defined for yasim aircraft but not for JSBSim aircraft
var winch_hash = {
open: props.globals.initNode("sim/hitches/winch/open", 1, "BOOL"), #always init to true
broken: props.globals.initNode("sim/hitches/winch/broken", 0, "BOOL"),
type: 0,
rope_breakage_p: props.globals.initNode("sim/hitches/winch/breakage/enabled", 0, "BOOL"), # NOT saved in config: Must be selected explicitly each time
rope_breakage: 0,
rope_breakage_height_int: 100.0,
loss_of_power: {
enabled_p: props.globals.initNode("sim/hitches/winch/loss-of-power/enabled", 0, "BOOL"), # NOT saved in config: Must be selected explicitly each time
enabled: 0,
height_int: 100.0,
},
global_pos: [
props.globals.initNode("sim/hitches/winch/winch/global-pos-x", 0.0, "DOUBLE"),
props.globals.initNode("sim/hitches/winch/winch/global-pos-y", 0.0, "DOUBLE"),
props.globals.initNode("sim/hitches/winch/winch/global-pos-z", 0.0, "DOUBLE"),
],
tow: {
length: props.globals.initNode("sim/hitches/winch/tow/length", 0.0, "DOUBLE"),
dist: props.globals.initNode("sim/hitches/winch/tow/dist", 0.0, "DOUBLE"),
},
old_open: props.globals.initNode("sim/hitches/winch/oldOpen", 1, "BOOL"),
rope: {
exist: props.globals.initNode("sim/hitches/winch/rope/exist", 0, "BOOL"),
model_id: props.globals.initNode("sim/hitches/winch/rope/model_id", -1, "INT"),
lat: props.globals.initNode("ai/models/winchrope/position/latitude-deg", 0.0, "DOUBLE"),
lon: props.globals.initNode("ai/models/winchrope/position/longitude-deg", 0.0, "DOUBLE"),
alt: props.globals.initNode("ai/models/winchrope/position/altitude-ft", 0.0, "DOUBLE"),
hdg: props.globals.initNode("ai/models/winchrope/orientation/true-heading-deg", 0.0, "DOUBLE"),
pitch: props.globals.initNode("ai/models/winchrope/orientation/pitch-deg", 0.0, "DOUBLE"),
},
};
var force_setting = 0.0; # used with constant force type winch
var speed_setting = 0.0; # used with constant speed type winch
if ( fdm == "jsb" ) {
# new properties for JSBSim aerotow
aircraft_settings.aerotow.force_name_jsbsim = check_or_create("sim/hitches/aerotow/force_name_jsbsim", "hitch", "STRING"); # aircraft-specific
aircraft_settings.aerotow.decoupled_locations = check_or_create("sim/hitches/aerotow/decoupled-force-and-rope-locations", 0, "BOOL"); # aircraft-specific
aircraft_settings.winch.force_name_jsbsim = check_or_create("sim/hitches/winch/force_name_jsbsim", "hitch", "STRING"); # aircraft-specific
aircraft_settings.winch.automatic_release_angle = check_or_create("sim/hitches/winch/automatic-release-angle-deg", 361.0, "DOUBLE"); # aircraft-specific
aircraft_settings.winch.decoupled_locations = check_or_create("sim/hitches/winch/decoupled-force-and-rope-locations", 0, "BOOL"); # aircraft-specific
aerotow_hash.mp_old_open = props.globals.initNode("sim/hitches/aerotow/mp_oldOpen", 1, "BOOL");
aerotow_hash.tow.mp_last_reported_dist = props.globals.initNode("sim/hitches/aerotow/tow/mp_last_reported_dist", 0.0, "DOUBLE");
# new properties for JSBSim winch
winch_hash.clutched = props.globals.initNode("sim/hitches/winch/winch/clutched", 0, "BOOL");
winch_hash.actual_spool_speed = props.globals.initNode("sim/hitches/winch/winch/actual-spool-speed-m-s", 0.0, "DOUBLE");
winch_hash.actual_force = props.globals.initNode("sim/hitches/winch/winch/actual-force-N", 0.0, "DOUBLE");
var hitchname = aircraft_settings.winch.force_name_jsbsim.getValue();
winch_hash.apply_force = {
magnitude: props.globals.initNode("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/magnitude", 0.0, "DOUBLE"),
coord: [
check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/x", 0.0, "DOUBLE"), # aircraft-specific
check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/y", 0.0, "DOUBLE"), # aircraft-specific
check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/z", 0.0, "DOUBLE"), # aircraft-specific
],
};
var hitchname = aircraft_settings.aerotow.force_name_jsbsim.getValue();
aerotow_hash.apply_force = {
magnitude: props.globals.initNode("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/magnitude", 0.0, "DOUBLE"),
coord: [
check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/x", 0.0, "DOUBLE"), # aircraft-specific
check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/y", 0.0, "DOUBLE"), # aircraft-specific
check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/z", 0.0, "DOUBLE"), # aircraft-specific
],
};
# consider older JSBSim-versions which do NOT provide the locations of external_reactions in the property tree
if( getprop("fdm/jsbsim/external_reactions/" ~ aircraft_settings.aerotow.force_name_jsbsim.getValue() ~ "/location-x-in") == nil ) {
aircraft_settings.aerotow.decoupled_locations.setBoolValue( 1 );
}
if( getprop("fdm/jsbsim/external_reactions/" ~ aircraft_settings.winch.force_name_jsbsim.getValue() ~ "/location-x-in") == nil ) {
aircraft_settings.winch.decoupled_locations.setBoolValue( 1 );
}
setlistener(aircraft_settings.winch.force_name_jsbsim , func() {
var hitchname = aircraft_settings.winch.force_name_jsbsim.getValue();
set_force_apply( winch_hash, hitchname );
});
setlistener(aircraft_settings.aerotow.force_name_jsbsim , func() {
var hitchname = aircraft_settings.aerotow.force_name_jsbsim.getValue();
set_force_apply( aerotow_hash, hitchname );
});
}
var callsign = check_or_create("sim/multiplay/callsign", "", "STRING"); # session-specific
var message_pilot = props.globals.getNode("sim/messages/pilot", 1);
var message_ai = props.globals.getNode("sim/messages/ai-plane", 1);
var message_atc = props.globals.getNode("sim/messages/atc", 1);
var write_message = func( type, message ){
if( type == "launch-signaller" ){
if( config.winch.messages.launch_signaller.getBoolValue() ) message_atc.setValue( message );
} elsif( type == "winch-driver" ){
if( config.winch.messages.winch_driver.getBoolValue() ) message_ai.setValue( message );
} elsif( type == "pilot" ){
if( config.winch.messages.pilot.getBoolValue() ) message_pilot.setValue( message );
} elsif( type == "remote-ac" ){
if( config.winch.messages.remote_ac.getBoolValue() ) message_ai.setValue( message );
} elsif( type == "system" ){
message_atc.setValue( message );
} else {
screen.log.write( message ~ " [type: unknown]");
}
}
var delta_time = props.globals.getNode("sim/time/delta-sec", 1);
var orientation = [
props.globals.getNode("orientation/heading-deg", 1),
props.globals.getNode("orientation/roll-deg", 1),
props.globals.getNode("orientation/pitch-deg", 1),
];
var wind_from = check_or_create("environment/wind-from-heading-deg", 0.0, "DOUBLE");
var wind_speed_kt = check_or_create("environment/wind-speed-kt", 0.0, "DOUBLE");
var set_force_apply = func ( hash, hitchname ){
hash.apply_force = {
magnitude: check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/magnitude", 0.0, "DOUBLE"),
coord: [
check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/x", 0.0, "DOUBLE"),
check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/y", 0.0, "DOUBLE"),
check_or_create("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/z", 0.0, "DOUBLE"),
],
};
}
var loss_of_power = func () {
# The maximum power is lost to 1/10 of it over a time span of 3 +- 1 seconds
interpolate( config.winch.max_power, config.winch.max_power.getDoubleValue() / 10, 2 + 2 * rand() );
# Afterwards it is completely lost after another 2 +- 1 seconds
settimer( func() {
interpolate( config.winch.max_power, 0, 1 + 2 * rand() );
}, 3);
}
# ######################################################################################################################
# main function
# ######################################################################################################################
var towing = func {
var dt = 0;
# ------------------------------- aerotow part -------------------------------
var open = aerotow_hash.open.getBoolValue();
var oldOpen = aerotow_hash.old_open.getBoolValue();
if ( open != oldOpen ) { # check if my hitch state has changed, if yes: message
if ( !open ) { # my hitch was open and is closed now
if ( fdm == "jsb" ) {
var distance = aerotow_hash.tow.dist.getDoubleValue();
var towlength_m = aerotow_hash.tow.length.getDoubleValue();
if ( distance > towlength_m * 1.0001 ) {
write_message( "system", sprintf("Could not lock hitch (tow length is insufficient) on hitch %i!", aerotow_hash.tow.conn_mp_node.getIntValue()) );
aerotow_hash.open.setBoolValue( 1 ); # open my hitch again
} else { # mp aircraft to far away
# my hitch is closed
write_message( "system", sprintf("Locked hitch aerotow %i!", aerotow_hash.tow.conn_mp_node.getIntValue()) );
}
aerotow_hash.broken.setBoolValue(0);
} # end: JSBSim
if ( !aerotow_hash.open.getBoolValue() ) {
# setup ai-towrope
createTowrope("aerotow");
# set default hitch coordinates (needed for Ai- and non-interactive MP aircraft)
setAIObjectDefaults() ;
}
} # end hitch is closed
if ( open ) { # my hitch is now open
if ( fdm == "jsb" ) {
if ( aerotow_hash.broken.getBoolValue() ) {
#getprop("sim/hitches/aerotow/broken")
write_message( "pilot", "Oh no, the tow is broken" );
} else {
write_message( "pilot", sprintf("Opened hitch aerotow %i!", aerotow_hash.tow.conn_mp_node.getIntValue() ) );
}
releaseHitch("aerotow"); # open=1 / forces=0
} # end: JSBSim
removeTowrope("aerotow"); # remove towrope model
} # end hitch is open
aerotow_hash.old_open.setBoolValue( open );
} # end hitch state has changed
if (!open ) {
aerotow(open);
# end hitch is closed (open == 0)
} else {
# my hitch is open
var mp_auto_connect_period = aerotow_hash.mp_auto_connect_period.getDoubleValue();
if ( mp_auto_connect_period != 0 ) { # if auto-connect
if ( fdm == "jsb" ) { # only for JSBSim aircraft
findBestAIObject();
} # end JSBSim aircraft
dt = mp_auto_connect_period;
} else { # my hitch is open and not auto-connect
dt = 0;
}
}
# winch part
if (!winch_hash.open.getBoolValue() ) {
winch(winch_hash.open.getBoolValue());
}
settimer( towing, dt );
} # end towing
setlistener( winch_hash.open, func {
var winchopen = winch_hash.open.getBoolValue();
var wincholdOpen = winch_hash.old_open.getBoolValue();
if ( winchopen != wincholdOpen ) { # check if my hitch state has changed, if yes: message
if ( !winchopen ) { # my hitch was open and is closed now
if ( fdm == "jsb" ) {
var distance = winch_hash.tow.dist.getDoubleValue();
var towlength_m = winch_hash.tow.length.getDoubleValue();
if ( distance > towlength_m ) {
write_message( "system", sprintf("Could not lock hitch (tow length is insufficient) on hitch %i!", aerotow_hash.tow.conn_mp_node.getIntValue() ) );
winch_hash.open.setBoolValue( 1 ); # open my hitch again
} else { # mp aircraft to far away
# my hitch is closed
write_message( "system", sprintf("Locked hitch winch %i!", aerotow_hash.tow.conn_mp_node.getIntValue() ) );
winch_hash.clutched.setBoolValue( 0 );;
}
winch_hash.broken.setBoolValue( 0 );
winch_hash.actual_spool_speed.setDoubleValue( 0.0 );
} # end: JSBSim
if ( !winch_hash.open.getBoolValue() ) {
# setup ai-towrope
createTowrope("winch");
# set default hitch coordinates (needed for Ai- and non-interactive MP aircraft)
setAIObjectDefaults() ;
}
} # end hitch is closed
if ( winchopen ) { # my hitch is now open
if ( fdm == "jsb" ) {
if ( winch_hash.broken.getBoolValue() ) {
write_message( "pilot", "Oh no, the tow is broken" );
}
releaseHitch("winch");
} # end: JSBSim
pull_in_rope();
} # end hitch is open
winch_hash.old_open.setBoolValue( winchopen );
} # end hitch state has changed
});
setlistener( aircraft_settings.winch.type_p, func {
winch_hash.type = aircraft_settings.winch.type_p.getIntValue();
});
# ######################################################################################################################
# find best AI object
# ######################################################################################################################
var findBestAIObject = func (){
# the nearest found plane, that is close enough will be used
# set some default variables, needed later to identify if the found object is
# an AI-Object, a "non-interactiv MP-Object or an interactive MP-Object
# local variables
var aiobjects = []; # keeps the ai-planes from the property tree
var aiPosition = geo.Coord.new(); # current processed ai-plane
var myPosition = geo.Coord.new(); # coordinates of glider
var distance_m = 0; # distance to ai-plane
var nodeIsAiAircraft = 0;
var nodeIsMpAircraft = 0;
var running_as_autoconnect = 0;
var mp_open_last_state = 0;
var isSlave = 0;
if ( fdm == "yasim" ) return; # bypass this routine for Yasim-aircraft
if (aerotow_hash.mp_auto_connect_period.getDoubleValue() != 0 ) {
var running_as_autoconnect = 1;
}
var towlength_m = aerotow_hash.tow.length.getDoubleValue();
var bestdist_m = towlength_m; # initial value
myPosition = geo.aircraft_position();
# todo: calculate exact hitch position
if( running_as_autoconnect ) {
var mycallsign = callsign.getValue();
}
var found = 0;
aiobjects = props.globals.getNode("ai/models").getChildren();
foreach (var aimember; aiobjects) {
if ( (var node = aimember.getName() ) != nil ) {
nodeIsAiAircraft = 0;
nodeIsMpAircraft = 0;
if ( sprintf("%8s",node) == "aircraft" or sprintf("%7s",node) == "dragger" ) nodeIsAiAircraft = 1;
if ( sprintf("%11s",node) == "multiplayer" ) nodeIsMpAircraft = 1;
if ( aimember.getNode("valid") == nil ) continue;
if ( !nodeIsAiAircraft and !nodeIsMpAircraft) continue;
if ( !aimember.getNode("valid").getValue() ) continue; # node is invalid
if( running_as_autoconnect ) {
if ( !nodeIsMpAircraft ) continue;
#if ( aimember.getValue("sim/hitches/aerotow/open") == nil ) continue; # this node MUST exist for mp-aircraft which want to be towed
#if ( aimember.getValue("sim/hitches/aerotow/open") == 1 ) continue; # if mp hook open, auto-connect is NOT possible
if ( aimember.getValue("sim/hitches/aerotow/open") != 0 ) continue;
if (mycallsign != aimember.getValue("sim/hitches/aerotow/tow/connected-to-ai-or-mp-callsign") ) continue ; # I am the wrong one
if ( !aerotow_hash.mp_old_open.getBoolValue() ) continue; # this prevents an unwanted immediate auto-connect after the dragger
# released its hitch. Firstly wait for a reported "open" hitch from glider
}
var lat_deg = aimember.getNode("position/latitude-deg").getValue();
var lon_deg = aimember.getNode("position/longitude-deg").getValue();
var alt_m = aimember.getNode("position/altitude-ft").getValue() * FT2M;
var aiPosition = geo.Coord.set_latlon( lat_deg, lon_deg, alt_m );
distance_m = (myPosition.distance_to(aiPosition));
if ( distance_m < bestdist_m ) {
bestdist_m = distance_m;
var towEndNode = node;
var nodeID = aimember.getNode("id").getValue();
var aicallsign = aimember.getNode("callsign").getValue();
#set properties
aerotow_hash.open.setBoolValue( 0 );
aerotow_hash.tow.conn_ai_node.setBoolValue( nodeIsAiAircraft );
aerotow_hash.tow.conn_mp_node.setBoolValue( nodeIsMpAircraft );
aerotow_hash.tow.conn_ai_or_mp_callsign.setValue( aicallsign );
aerotow_hash.tow.conn_ai_or_mp_id.setIntValue( nodeID );
aerotow_hash.tow.conn_prop_node.setBoolValue( 1 );
aerotow_hash.tow.node.setValue( towEndNode );
aerotow_hash.tow.dist.setDoubleValue( bestdist_m );
aerotow_hash.tow.mp_last_reported_dist.setDoubleValue( 0.0 );
# Set some dummy values. In case of an "interactive"-MP plane
# the correct values will be transmitted in the following loop
aimember.getNode("sim/hitches/aerotow/local-pos-x",1).setValue(-5.);
aimember.getNode("sim/hitches/aerotow/local-pos-y",1).setValue(0.);
aimember.getNode("sim/hitches/aerotow/local-pos-z",1).setValue(0.);
aimember.getNode("sim/hitches/aerotow/tow/dist",1).setValue(-1.);
found = 1;
} # end distance_m < bestdist_m
} # end node != nil
} # end loop aiobjects
if (found) {
if ( !running_as_autoconnect) {
write_message( "pilot", sprintf("%s, I am on your hook, distance %4.3f meter.",aicallsign,bestdist_m) );
} else {
write_message( "remote-ac", sprintf("%s: I am on your hook, distance %4.3f meter.",aicallsign,bestdist_m ) );
}
if ( running_as_autoconnect ) {
isSlave = 1;
aerotow_hash.is_slave.setBoolValue( isSlave );
}
aerotow_hash.mp_old_open.setBoolValue( 1 );
} # end: if found
else {
if (!running_as_autoconnect) {
write_message( "system", "Sorry, no aircraft for aerotow!" );
} else{
aerotow_hash.old_open.setBoolValue( 1 );
}
}
} # End function findBestAIObject
# ######################################################################################################################
# Start the towing animation ASAP
towing();
# ######################################################################################################################
# aerotow function
# ######################################################################################################################
var aerotow = func (open){
#print("function aerotow is running");
# if (!open ) {
########################################### my hitch position ############################################
myPosition = geo.aircraft_position();
var my_head_deg = orientation[0].getDoubleValue();
var my_roll_deg = orientation[1].getDoubleValue();
var my_pitch_deg = orientation[2].getDoubleValue();
# hook coordinates in Yasim-system (x-> nose / y -> left wing / z -> up)
assignHitchLocations("aerotow");
var x = aircraft_settings.aerotow.local_pos[0].getDoubleValue();
var y = aircraft_settings.aerotow.local_pos[1].getDoubleValue();
var z = aircraft_settings.aerotow.local_pos[2].getDoubleValue();
var alpha_deg = my_roll_deg * (1.); # roll clockwise (looking in x-direction) := +
var beta_deg = my_pitch_deg * (-1.); # pitch clockwise (looking in y-direction) := -
# transform hook coordinates
var Xn = PointRotate3D(x:x,y:y,z:z,xr:0.,yr:0.,zr:0.,alpha_deg:alpha_deg,beta_deg:beta_deg,gamma_deg:0.);
var install_distance_m = Xn[0]; # in front of ref-point of glider
var install_side_m = Xn[1];
var install_alt_m = Xn[2];
var myHitch_pos = myPosition.apply_course_distance( my_head_deg , install_distance_m );
var myHitch_pos = myPosition.apply_course_distance( my_head_deg - 90. , install_side_m );
myHitch_pos.set_alt(myPosition.alt() + install_alt_m);
########################################### ai hitch position ############################################
#var aiNodeID = getprop("sim/hitches/aerotow/tow/connected-to-ai-or-mp-id"); # id of former found ai/mp aircraft
#print("aiNodeID=",aiNodeID);
var aiCallsign = aerotow_hash.tow.conn_ai_or_mp_callsign.getValue(); # callsign of former found ai/mp aircraft
var found = 0;
aiobjects = props.globals.getNode("ai/models").getChildren();
foreach (var aimember; aiobjects) {
if ( (var c = aimember.getNode("id") ) != nil ) {
if ( !aimember.getNode("valid").getValue() ) continue; # node is invalid
# Identifying the MP-aircraft by its node-id works fine with JSBSim-aircraft but NOT with YASim.
# In YASim the node-id is not updated which could lead to complications (e.g. node-id changes after "Pause" or "Exit").
#var testprop = c.getValue();
#if ( testprop == aiNodeID) {
# Identifying the MP-aircraft by its callsign works fine with JSBSim AND YASim-aircraft
var testprop = aimember.getNode("callsign").getValue();
if ( testprop == aiCallsign ) {
found = found + 1;
###################### check status of ai hitch ######################
if ( fdm == "jsb" ) {
# check if the multiplayer hitch state has changed
# this trick avoids immediate opening after locking because MP-aircraft has not yet reported a locked hitch
if ( (var d = aimember.getNode("sim/hitches/aerotow/open") ) != nil ) {
var mpOpen = aimember.getNode("sim/hitches/aerotow/open").getValue();
var mp_oldOpen = aerotow_hash.mp_old_open.getBoolValue();
#print('mpOpen=',mpOpen,' mp_oldOpen=',mp_oldOpen);
if ( mpOpen != mp_oldOpen ) { # state has changed: was open and is now locked OR was locked and is now open
if ( mpOpen ) {
write_message( "remote-ac", sprintf("%s: I have released the tow!", aerotow_hash.tow.conn_ai_or_mp_callsign.getValue() ) );
releaseHitch("aerotow"); # my open=1 / forces=0 / remove towrope
} # end: open
aerotow_hash.mp_old_open.setBoolValue(mpOpen);
} # end: state has changed
} # end: node is available
} #end : JSBSim
########################################################################
# get coordinates
var ai_lat = aimember.getNode("position/latitude-deg").getValue();
var ai_lon = aimember.getNode("position/longitude-deg").getValue();
var ai_alt = (aimember.getNode("position/altitude-ft").getValue()) * FT2M;
#print("ai_lat,lon,alt",ai_lat,ai_lon,ai_alt);
var ai_pitch_deg = aimember.getNode("orientation/pitch-deg").getValue();
var ai_roll_deg = aimember.getNode("orientation/roll-deg").getValue();
var ai_head_deg = aimember.getNode("orientation/true-heading-deg").getValue();
var aiHitchX = aimember.getNode("sim/hitches/aerotow/local-pos-x").getValue();
var aiHitchY = aimember.getNode("sim/hitches/aerotow/local-pos-y").getValue();
var aiHitchZ = aimember.getNode("sim/hitches/aerotow/local-pos-z").getValue();
var aiPosition = geo.Coord.set_latlon( ai_lat, ai_lon, ai_alt );
var alpha_deg = ai_roll_deg * (1.);
var beta_deg = ai_pitch_deg * (-1.);
# transform hook coordinates
var Xn = PointRotate3D(x:aiHitchX,y:aiHitchY,z:aiHitchZ,xr:0.,yr:0.,zr:0.,alpha_deg:alpha_deg,beta_deg:beta_deg,gamma_deg:0.);
var install_distance_m = Xn[0]; # in front of ref-point of glider
var install_side_m = Xn[1];
var install_alt_m = Xn[2];
var aiHitch_pos = aiPosition.apply_course_distance( ai_head_deg , install_distance_m );
var aiHitch_pos = aiPosition.apply_course_distance( ai_head_deg - 90. , install_side_m );
aiHitch_pos.set_alt(aiPosition.alt() + install_alt_m);
########################################### distance between hitches #####################################
var distance = (myHitch_pos.direct_distance_to(aiHitch_pos)); # distance to plane in meter
var aiHitchheadto = (myHitch_pos.course_to(aiHitch_pos));
var height = myHitch_pos.alt() - aiHitch_pos.alt();
var aiHitchpitchto = -math.asin((myHitch_pos.alt()-aiHitch_pos.alt())/distance) / 0.01745;
#print(" pitch: ", aiHitchpitchto);
# update position of rope
aerotow_hash.rope.lat.setDoubleValue( myHitch_pos.lat() );
aerotow_hash.rope.lon.setDoubleValue( myHitch_pos.lon() );
aerotow_hash.rope.alt.setDoubleValue( myHitch_pos.alt() * M2FT );
# update orientation of rope
aerotow_hash.rope.hdg.setDoubleValue( aiHitchheadto );
aerotow_hash.rope.pitch.setDoubleValue( aiHitchpitchto );
# update length of rope
aerotow_hash.tow.dist.setDoubleValue( distance );
############################################# calc forces ##################################################
# calc forces only for JSBSim-aircraft
# tow-end-forces must be reported in N to be consiststent to Yasim-aircraft
# hitch-forces must be LBS to be consistent to the JSBSim "external_forces/.../magnitude" definition
if ( fdm == "jsb" ) {
# check if the MP-aircraft properties have been updated. If not (maybe due to time-lag) bypass force calculation (use previous forces instead)
var mp_reported_dist = aimember.getNode("sim/hitches/aerotow/tow/dist").getValue();
var mp_last_reported_dist = aerotow_hash.tow.mp_last_reported_dist.getDoubleValue();
var mp_delta_reported_dist = mp_reported_dist - mp_last_reported_dist ;
aerotow_hash.tow.mp_last_reported_dist.setDoubleValue( mp_reported_dist );
var mp_delta_reported_dist2 = mp_delta_reported_dist * mp_delta_reported_dist ; # we need the absolute value
if ( (mp_delta_reported_dist2 > 0.0000001) or (mp_reported_dist < 0. )){ # we have the updated MP coordinates (no time lag)
# or the MP-aircraft is a non-interactive mp plane (mp_reported_dist = -1)
# => update forces else use the old forces!
var breakforce_N = getprop("sim/hitches/aerotow/tow/break-force"); # could be different in both aircraft
var isSlave = aerotow_hash.is_slave.getBoolValue();
if ( !isSlave ){ # if we are master, we have to calculate the forces
var elastic_constant = getprop("sim/hitches/aerotow/tow/elastic-constant");
var towlength_m = getprop("sim/hitches/aerotow/tow/length");
var delta_towlength_m = distance - towlength_m;
if ( delta_towlength_m < 0. ) {
var forcetow_N = 0.;
}
else{
var forcetow_N = elastic_constant * delta_towlength_m / towlength_m;
}
} else { # we are slave and get the forces from master
var forcetowX_N = aimember.getNode("sim/hitches/aerotow/tow/end-force-x").getValue() * 1;
var forcetowY_N = aimember.getNode("sim/hitches/aerotow/tow/end-force-y").getValue() * 1;
var forcetowZ_N = aimember.getNode("sim/hitches/aerotow/tow/end-force-z").getValue() * 1;
var forcetow_N = math.sqrt( forcetowX_N * forcetowX_N + forcetowY_N * forcetowY_N + forcetowZ_N * forcetowZ_N );
}
var forcetow_LBS = forcetow_N * 0.224809; # N -> LBF
if ( forcetow_N < breakforce_N ) {
var distancepr = (myHitch_pos.distance_to(aiHitch_pos));
# correct a failure, if the projected length is larger than direct length
if (distancepr > distance) { distancepr = distance;}
var alpha = math.acos( (distancepr / distance) );
if ( aiHitch_pos.alt() > myHitch_pos.alt()) alpha = - alpha;
var beta = ( aiHitchheadto - my_head_deg ) * 0.01745;
var gamma = my_pitch_deg * 0.01745;
var delta = my_roll_deg * 0.01745;
var sina = math.sin(alpha);
var cosa = math.cos(alpha);
var sinb = math.sin(beta);
var cosb = math.cos(beta);
var sing = math.sin(gamma);
var cosg = math.cos(gamma);
var sind = math.sin(delta);
var cosd = math.cos(delta);
var forcetow = forcetow_LBS; # we deliver LBS to JSBSim
# calculate unit vector of force direction in JSBSim-system
var force = 1;
# global forces: alpha beta
var fglobalx = force * cosa * cosb;
var fglobaly = force * cosa * sinb;
var fglobalz = force * sina;
# local forces by pitch: gamma
var flpitchx = fglobalx * cosg - fglobalz * sing;
var flpitchy = fglobaly;
var flpitchz = fglobalx * sing + fglobalz * cosg;
# local forces by roll: delta
var flrollx = flpitchx;
var flrolly = flpitchy * cosd + flpitchz * sind;
var flrollz = - flpitchy * sind + flpitchz * cosd;
# asigning to LOCAL coord of plane
var forcex = flrollx;
var forcey = flrolly;
var forcez = flrollz;
# JSBSim-body-frame: x-> nose / y -> right wing / z -> down
# apply forces to hook (forces are in LBS or N see above)
var hitchname = getprop("sim/hitches/aerotow/force_name_jsbsim");
setprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/magnitude", forcetow);
setprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/x", forcex);
setprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/y", forcey);
setprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/z", forcez);
} else { # rope is broken
aerotow_hash.broken.setBoolValue( 1 );
#setprop("sim/messages/atc", sprintf("Oh no, the tow is broken"));
releaseHitch("aerotow"); # open=1 / forces=0 / remove towrope
}
############################################# report forces ##############################################
# if we are connected to a MP-aircraft and master
var nodeIsMpAircraft = getprop("sim/hitches/aerotow/tow/connected-to-mp-node");
if ( nodeIsMpAircraft and !isSlave ){
# transform my hitch coordinates to cartesian earth coordinates
var myHitchCartEarth = geodtocart(myHitch_pos.lat(),myHitch_pos.lon(),myHitch_pos.alt() );
var myHitchXearth_m = myHitchCartEarth[0];
var myHitchYearth_m = myHitchCartEarth[1];
var myHitchZearth_m = myHitchCartEarth[2];
# transform MP hitch coordinates to cartesian earth coordinates
var aiHitchCartEarth = geodtocart(aiHitch_pos.lat(),aiHitch_pos.lon(),aiHitch_pos.alt() );
var aiHitchXearth_m = aiHitchCartEarth[0];
var aiHitchYearth_m = aiHitchCartEarth[1];
var aiHitchZearth_m = aiHitchCartEarth[2];
# calculate normal vector in tow direction in cartesian earth coordinates
var dx = aiHitchXearth_m - myHitchXearth_m;
var dy = aiHitchYearth_m - myHitchYearth_m;
var dz = aiHitchZearth_m - myHitchZearth_m;
var dl = math.sqrt( dx * dx + dy * dy + dz * dz );
var forcetowX_N = forcetow_N * dx / dl;
var forcetowY_N = forcetow_N * dy / dl;
var forcetowZ_N = forcetow_N * dz / dl;
setprop("sim/hitches/aerotow/tow/dist", distance);
setprop("sim/hitches/aerotow/tow/end-force-x", -forcetowX_N); # force acts in
setprop("sim/hitches/aerotow/tow/end-force-y", -forcetowY_N); # opposite direction
setprop("sim/hitches/aerotow/tow/end-force-z", -forcetowZ_N); # at tow end
} # end report forces
} # end: timelag
} # end forces/JSBSim
} # end: aiNodeID
} # end: check id != nil
} # end: loop over aiobjects
if ( found == 0 ) {
if ( fdm == "jsb" ) {
write_message( "system", "MP-aircraft disappeared!" );
aerotow_hash.open.setBoolValue( 1 ); # open my hitch
aerotow_hash.tow.conn_ai_or_mp_id.setIntValue( 0 );
aerotow_hash.tow.conn_ai_or_mp_callsign.setValue( "" );
aerotow_hash.tow.conn_ai_node.setBoolValue( 0 );
aerotow_hash.tow.conn_mp_node.setBoolValue( 0 );
aerotow_hash.tow.conn_prop_node.setBoolValue( 0 );
}
#if ( fdm == "yasim" ) removeTowrope("aerotow"); # remove towrope model
} # end found=0
} # end function aerotow
# ######################################################################################################################
# winch function
# ######################################################################################################################
var winch = func (open){
if (!open ) {
########################################### my hitch position ############################################
myPosition = geo.aircraft_position();
var my_head_deg = orientation[0].getDoubleValue();
var my_roll_deg = orientation[1].getDoubleValue();
var my_pitch_deg = orientation[2].getDoubleValue();
# hook coordinates in Yasim-system (x-> nose / y -> left wing / z -> up)
assignHitchLocations("winch");
var x = aircraft_settings.winch.local_pos[0].getDoubleValue();
var y = aircraft_settings.winch.local_pos[1].getDoubleValue();
var z = aircraft_settings.winch.local_pos[2].getDoubleValue();
var alpha_deg = my_roll_deg * (1.); # roll clockwise (looking in x-direction) := +
var beta_deg = my_pitch_deg * (-1.); # pitch clockwise (looking in y-direction) := -
# transform hook coordinates
var Xn = PointRotate3D(x:x,y:y,z:z,xr:0.,yr:0.,zr:0.,alpha_deg:alpha_deg,beta_deg:beta_deg,gamma_deg:0.);
var install_distance_m = Xn[0]; # in front of ref-point of glider
var install_side_m = Xn[1];
var install_alt_m = Xn[2];
var myHitch_pos = myPosition.apply_course_distance( my_head_deg , install_distance_m );
var myHitch_pos = myPosition.apply_course_distance( my_head_deg - 90. , install_side_m );
myHitch_pos.set_alt(myPosition.alt() + install_alt_m);
############################ Check for rope breakage ###############################
var my_height = myHitch_pos.alt() - geo.elevation( myHitch_pos.lat(), myHitch_pos.lon() );
if( winch_hash.rope_breakage and my_height > winch_hash.rope_breakage_height_int ) {
releaseHitch("winch");
winch_hash.rope_breakage = 0;
# The pilot shouldn't immediately be notified this was a rope breakage (to train recognizing a rope breakage)
# That's why we report the breakage and its height 5 seconds later
var temp = winch_hash.rope_breakage_height_int;
settimer( func() {
write_message( "winch-driver", "Rope broke at " ~ sprintf("%4d", math.round( temp ) ) ~ "m height" );
}, 5);
return;
}
############################ Check for winch loss of power ##########################
if( winch_hash.loss_of_power.enabled and my_height > winch_hash.loss_of_power.height_int ) {
loss_of_power();
winch_hash.loss_of_power.enabled = 0;
# The pilot shouldn't immediately be notified this was a loss of power (to train recognizing a winch loss of power)
# That's why we report the loss of power and its height 15 seconds later
var temp = winch_hash.loss_of_power.height_int;
settimer( func() {
write_message( "winch-driver", "Winch lost power at " ~ sprintf("%4d", math.round( temp ) ) ~ "m height" );
}, 15);
return;
}
########################################### winch hitch position ############################################
# get coordinates
var winch_global_pos_x = winch_hash.global_pos[0].getDoubleValue();
var winch_global_pos_y = winch_hash.global_pos[1].getDoubleValue();
var winch_global_pos_z = winch_hash.global_pos[2].getDoubleValue();
var winch_geod = carttogeod(winch_global_pos_x, winch_global_pos_y, winch_global_pos_z);
var ai_lat = winch_geod[0];
var ai_lon = winch_geod[1];
#var ai_alt = winch_geod[2] * FT2M;
var ai_alt = winch_geod[2];
#print("ai_lat,lon,alt",ai_lat,ai_lon,ai_alt);
var aiHitch_pos = geo.Coord.set_latlon( ai_lat, ai_lon, ai_alt );
########################################### distance between hitches #####################################
var distance = (myHitch_pos.direct_distance_to(aiHitch_pos)); # distance to winch in meter
var aiHitchheadto = (myHitch_pos.course_to(aiHitch_pos));
var height = myHitch_pos.alt() - aiHitch_pos.alt();
var aiHitchpitchto = -math.asin((myHitch_pos.alt()-aiHitch_pos.alt())/distance) * R2D;
#print(" pitch: ", aiHitchpitchto);
#print("Update Winch Rope");
#print("Winch Rope Lat is "~ myHitch_pos.lat() );
# update position of rope
winch_hash.rope.lat.setDoubleValue( myHitch_pos.lat() );
winch_hash.rope.lon.setDoubleValue( myHitch_pos.lon() );
winch_hash.rope.alt.setDoubleValue( myHitch_pos.alt() * M2FT );
# update orientation of rope
winch_hash.rope.hdg.setDoubleValue( aiHitchheadto );
winch_hash.rope.pitch.setDoubleValue( aiHitchpitchto );
# update length of rope
winch_hash.tow.dist.setDoubleValue( distance );
#print("distance=",distance);
############################################# calc forces ##################################################
# calc forces only for JSBSim-aircraft
# tow-end-forces must be reported in N to be consiststent to Yasim-aircraft
# hitch-forces must be LBS to be consistent to the JSBSim "external_forces/.../magnitude" definition
if ( fdm == "jsb" ) {
var spool_max = config.winch.max_spool_speed.getDoubleValue();
var unspool_max = config.winch.max_unspool_speed.getDoubleValue();
var max_force_N = config.winch.max_force.getDoubleValue();
var max_power_W = config.winch.max_power.getDoubleValue() * 1000.;
var breakforce_N = aircraft_settings.winch.break_force.getDoubleValue();
var elastic_constant = config.winch.elastic_constant.getDoubleValue();
var towlength_m = winch_hash.tow.length.getDoubleValue();
var max_tow_length_m = config.winch.max_tow_length_m.getDoubleValue();
var spoolspeed = winch_hash.actual_spool_speed.getDoubleValue();
var spool_acceleration = config.winch.spool_acceleration.getDoubleValue();
var delta_t = delta_time.getDoubleValue();
#print("towlength_m= ", towlength_m , " elastic_constant= ", elastic_constant," delta_towlength_m= ", delta_towlength_m);
var forcetow_N = winch_hash.actual_force.getDoubleValue();
var towlength_new_m = nil;
var delta_towlength_m = nil;
if ( winch_hash.clutched.getBoolValue() ) {
if( winch_hash.type == 0 ){
# Concept: force is given
forcetow_N = force_setting;
} else if ( winch_hash.type == 1 ){
# Concept: regulate force to reach target speed
var speed_trgt = speed_setting;
if( math.abs ( spoolspeed - speed_trgt ) > 0.5 ) {
if( speed_trgt > spoolspeed ) {
forcetow_N += acceleration_N_s * delta_t;
} else if ( speed_trgt < spoolspeed ){
forcetow_N -= acceleration_N_s * delta_t;
}
}
}
# New towlength is distance from hook to winch; previous towlength is stored in winch_hash.towlength_m
towlength_new_m = distance;
delta_towlength_m = -1 * ( distance - towlength_m );
spoolspeed = delta_towlength_m / delta_t;
} else { # un-clutched
# --- experimental --- #
towlength_new_m = towlength_m - spoolspeed * delta_t;
delta_towlength_m = distance - towlength_new_m;
# we assume that the the winch-operator avoids tow sagging ( => rigid rope; negativ forces allowed)
forcetow_N = elastic_constant * delta_towlength_m / towlength_new_m;
# drag of tow-rope ( magic! )
var magic_constant = config.winch.magic_constant.getDoubleValue();
tow_drag_N = spoolspeed * spoolspeed * math.sqrt( math.sqrt( height * height ) * max_tow_length_m ) / magic_constant ;
# mass = tow-mass only (drum-mass ignored)
var mass_kg = max_tow_length_m * config.winch.weight_per_m_kg_m.getDoubleValue();
var acceleration = ( forcetow_N - tow_drag_N ) / mass_kg;
var delta_spoolspeed = acceleration * delta_t;
spoolspeed = spoolspeed - delta_spoolspeed;
if ( spoolspeed < - unspool_max ) spoolspeed = - unspool_max;
#if ( delta_towlength_m < 0. ) {
# forcetow_N = 0.;
#}
}
if ( forcetow_N > max_force_N ) {
forcetow_N = max_force_N;
var towlength_new_m = distance / ( forcetow_N / elastic_constant + 1. );
spoolspeed = (towlength_m - towlength_new_m ) / delta_t;
}
var power = forcetow_N * spoolspeed;
if ( power > max_power_W) {
power = max_power_W;
forcetow_N = power / spoolspeed;
towlength_new_m = towlength_m - spoolspeed * delta_t;
}
winch_hash.tow.length.setDoubleValue( towlength_new_m );
winch_hash.actual_spool_speed.setDoubleValue( spoolspeed );
winch_hash.actual_force.setDoubleValue( forcetow_N );
# force due to tow-weight (acts in tow direction at the heigher hitch)
var force_due_to_weight_N = config.winch.weight_per_m_kg_m.getDoubleValue() * 9.81 * height;
if (height < 0. ) force_due_to_weight_N = 0.;
forcetow_N = forcetow_N + force_due_to_weight_N;
var forcetow_LBS = forcetow_N * 0.224809; # N -> LBF
if ( forcetow_N < breakforce_N ) {
var distancepr = (myHitch_pos.distance_to(aiHitch_pos));
# correct a failure, if the projected length is larger than direct length
if (distancepr > distance) { distancepr = distance;}
var alpha = math.acos( (distancepr / distance) );
if ( aiHitch_pos.alt() > myHitch_pos.alt()) alpha = - alpha;
var beta = ( aiHitchheadto - my_head_deg ) * D2R;
var gamma = my_pitch_deg * D2R;
var delta = my_roll_deg * D2R;
var sina = math.sin(alpha);
var cosa = math.cos(alpha);
var sinb = math.sin(beta);
var cosb = math.cos(beta);
var sing = math.sin(gamma);
var cosg = math.cos(gamma);
var sind = math.sin(delta);
var cosd = math.cos(delta);
# calculate unit vector of force direction in JSBSim-system
var force = 1;
# global forces: alpha beta
var fglobalx = force * cosa * cosb;
var fglobaly = force * cosa * sinb;
var fglobalz = force * sina;
# local forces by pitch: gamma
var flpitchx = fglobalx * cosg - fglobalz * sing;
var flpitchy = fglobaly;
var flpitchz = fglobalx * sing + fglobalz * cosg;
# local forces by roll: delta
var flrollx = flpitchx;
var flrolly = flpitchy * cosd + flpitchz * sind;
var flrollz = - flpitchy * sind + flpitchz * cosd;
# asigning to LOCAL coord of plane
var forcex = flrollx;
var forcey = flrolly;
var forcez = flrollz;
# JSBSim-body-frame: x-> nose / y -> right wing / z -> down
# apply forces to hook (forces are in LBS or N see above)
winch_hash.apply_force.magnitude.setDoubleValue( forcetow_LBS );
winch_hash.apply_force.coord[0].setDoubleValue( forcex );
winch_hash.apply_force.coord[1].setDoubleValue( forcey );
winch_hash.apply_force.coord[2].setDoubleValue( forcez );
# check, if auto-release condition is reached
var rope_angle_deg = math.atan2(forcez , forcex ) * R2D;
#print("rope_angle_deg=",rope_angle_deg);
if (rope_angle_deg > aircraft_settings.winch.automatic_release_angle.getDoubleValue() ) releaseWinch();
} # end force < break force
else { # rope is broken
winch_hash.broken.setBoolValue( 1 );
releaseWinch();
}
if ( towlength_new_m > max_tow_length_m ) {
write_message( "system", "tow length exceeded!" );
releaseWinch();
}
} # end forces/JSBSim
} # end hitch is closed (open == 0)
} # end function winch
# ######################################################################################################################
# create towrope
# ######################################################################################################################
var createTowrope = func (device){
# create the towrope in the model property tree
#print("createTowrope for ",device);
if ( getprop("sim/hitches/" ~ device ~ "/rope/exist") == 0 ) { # does the towrope exist?
# get the next free model id
var freeModelid = getFreeModelID();
props.globals.getNode("sim/hitches/" ~ device ~ "/rope/model_id").setIntValue(freeModelid);
props.globals.getNode("sim/hitches/" ~ device ~ "/rope/exist").setBoolValue(1);
var towrope_ai = props.globals.getNode("ai/models/" ~ device ~ "rope", 1);
var towrope_mod = props.globals.getNode("models", 1);
towrope_ai.getNode("id", 1).setIntValue(4711);
towrope_ai.getNode("callsign", 1).setValue("towrope");
towrope_ai.getNode("valid", 1).setBoolValue(1);
towrope_ai.getNode("position/latitude-deg", 1).setValue(0.);
towrope_ai.getNode("position/longitude-deg", 1).setValue(0.);
towrope_ai.getNode("position/altitude-ft", 1).setValue(0.);
towrope_ai.getNode("orientation/true-heading-deg", 1).setValue(0.);
towrope_ai.getNode("orientation/pitch-deg", 1).setValue(0.);
towrope_ai.getNode("orientation/roll-deg", 1).setValue(0.);
towrope_mod.model = towrope_mod.getChild("model", freeModelid, 1);
towrope_mod.model.getNode("path", 1).setValue(getprop("sim/hitches/" ~ device ~ "/rope/path_to_model") );
towrope_mod.model.getNode("longitude-deg-prop", 1).setValue("ai/models/" ~ device ~ "rope/position/longitude-deg");
towrope_mod.model.getNode("latitude-deg-prop", 1).setValue("ai/models/" ~ device ~ "rope/position/latitude-deg");
towrope_mod.model.getNode("elevation-ft-prop", 1).setValue("ai/models/" ~ device ~ "rope/position/altitude-ft");
towrope_mod.model.getNode("heading-deg-prop", 1).setValue("ai/models/" ~ device ~ "rope/orientation/true-heading-deg");
towrope_mod.model.getNode("roll-deg-prop", 1).setValue("ai/models/" ~ device ~ "rope/orientation/roll-deg");
towrope_mod.model.getNode("pitch-deg-prop", 1).setValue("ai/models/" ~ device ~ "rope/orientation/pitch-deg");
towrope_mod.model.getNode("load", 1).remove();
if( device == "winch" ){
winch_hash.rope.lat= check_or_create("ai/models/winchrope/position/latitude-deg", 0.0, "DOUBLE");
winch_hash.rope.lon= check_or_create("ai/models/winchrope/position/longitude-deg", 0.0, "DOUBLE");
winch_hash.rope.alt= check_or_create("ai/models/winchrope/position/altitude-ft", 0.0, "DOUBLE");
winch_hash.rope.hdg= check_or_create("ai/models/winchrope/orientation/true-heading-deg", 0.0, "DOUBLE");
winch_hash.rope.pitch= check_or_create("ai/models/winchrope/orientation/pitch-deg", 0.0, "DOUBLE");
}
} # end towrope exist
}
# ######################################################################################################################
# get the next free id of "models/model" members
# ######################################################################################################################
var getFreeModelID = func {
#print("getFreeModelID");
var modelid = 0; # next unused id
modelobjects = props.globals.getNode("models", 1).getChildren();
foreach ( var member; modelobjects ) {
if ( (var c = member.getIndex()) != nil) {
modelid = c + 1;
}
}
#print("modelid=",modelid);
return(modelid);
}
# ######################################################################################################################
# close aerotow hitch
# ######################################################################################################################
var closeHitch = func {
#print("closeHitch");
# close only, if
# - not yet closed
# - connected to property-node
# - distance < towrope length
if ( !aerotow_hash.open.getBoolValue() ) return;
var aiNodeID = getprop("sim/hitches/aerotow/tow/connected-to-ai-or-mp-id"); # id of former found ai/mp aircraft
if ( aiNodeID < 1 ) {
setprop("sim/messages/atc", sprintf("No aircraft selected!"));
return;
}
##################################### calc distance between hitches ######################
###################### my hitch position #######################
myPosition = geo.aircraft_position();
var my_head_deg = getprop("orientation/heading-deg");
var my_roll_deg = getprop("orientation/roll-deg");
var my_pitch_deg = getprop("orientation/pitch-deg");
# hook coordinates in Yasim-system (x-> nose / y -> left wing / z -> up)
assignHitchLocations("aerotow");
var x = aircraft_settings.aerotow.local_pos[0].getDoubleValue();
var y = aircraft_settings.aerotow.local_pos[1].getDoubleValue();
var z = aircraft_settings.aerotow.local_pos[2].getDoubleValue();
#var x = getprop("sim/hitches/aerotow/local-pos-x");
#var y = getprop("sim/hitches/aerotow/local-pos-y");
#var z = getprop("sim/hitches/aerotow/local-pos-z");
var alpha_deg = my_roll_deg * (1.); # roll clockwise (looking in x-direction) := +
var beta_deg = my_pitch_deg * (-1.); # pitch clockwise (looking in y-direction) := -
# transform hook coordinates
var Xn = PointRotate3D(x:x,y:y,z:z,xr:0.,yr:0.,zr:0.,alpha_deg:alpha_deg,beta_deg:beta_deg,gamma_deg:0.);
var install_distance_m = Xn[0]; # in front of ref-point of glider
var install_side_m = Xn[1];
var install_alt_m = Xn[2];
var myHitch_pos = myPosition.apply_course_distance( my_head_deg , install_distance_m );
var myHitch_pos = myPosition.apply_course_distance( my_head_deg - 90. , install_side_m );
myHitch_pos.set_alt(myPosition.alt() + install_alt_m);
###################### ai hitch position #######################
var found = 0;
aiobjects = props.globals.getNode("ai/models").getChildren();
foreach (var aimember; aiobjects) {
if ( (var c = aimember.getNode("id") ) != nil ) {
var testprop = c.getValue();
if ( testprop == aiNodeID) {
found = found + 1;
# get coordinates
var ai_lat = aimember.getNode("position/latitude-deg").getValue();
var ai_lon = aimember.getNode("position/longitude-deg").getValue();
var ai_alt = (aimember.getNode("position/altitude-ft").getValue()) * FT2M;
var ai_pitch_deg = aimember.getNode("orientation/pitch-deg").getValue();
var ai_roll_deg = aimember.getNode("orientation/roll-deg").getValue();
var ai_head_deg = aimember.getNode("orientation/true-heading-deg").getValue();
var aiHitchX = aimember.getNode("sim/hitches/aerotow/local-pos-x").getValue();
var aiHitchY = aimember.getNode("sim/hitches/aerotow/local-pos-y").getValue();
var aiHitchZ = aimember.getNode("sim/hitches/aerotow/local-pos-z").getValue();
var aiPosition = geo.Coord.set_latlon( ai_lat, ai_lon, ai_alt );
var alpha_deg = ai_roll_deg * (1.);
var beta_deg = ai_pitch_deg * (-1.);
# transform hook coordinates
var Xn = PointRotate3D(x:aiHitchX,y:aiHitchY,z:aiHitchZ,xr:0.,yr:0.,zr:0.,alpha_deg:alpha_deg,beta_deg:beta_deg,gamma_deg:0.);
var install_distance_m = Xn[0]; # in front of ref-point of glider
var install_side_m = Xn[1];
var install_alt_m = Xn[2];
var aiHitch_pos = aiPosition.apply_course_distance( ai_head_deg , install_distance_m );
var aiHitch_pos = aiPosition.apply_course_distance( ai_head_deg - 90. , install_side_m );
aiHitch_pos.set_alt(aiPosition.alt() + install_alt_m);
var distance = (myHitch_pos.direct_distance_to(aiHitch_pos));
var towlength_m = props.globals.getNode("sim/hitches/aerotow/tow/length").getValue();
if ( distance > towlength_m ) {
var aicallsign = getprop("sim/hitches/aerotow/tow/connected-to-ai-or-mp-callsign");
#setprop("sim/messages/atc", sprintf("Aircraft with callsign %s is too far away (distance is %4.0f meter).",aicallsign, distance));
setprop("sim/messages/atc", sprintf("Selected aircraft is too far away (distance to %s is %4.0f meter).",aicallsign, distance));
return;
}
setprop("sim/hitches/aerotow/tow/dist", distance);
}
}
}
aerotow_hash.open.setBoolValue( 0 );
setprop("sim/hitches/aerotow/mp_oldOpen", "true");
} # End function closeHitch
# ######################################################################################################################
# release hitch
# ######################################################################################################################
var releaseHitch = func (device){
#print("releaseHitch");
if ( fdm == "yasim" ) return; # bypass this routine for Yasim-aircraft
setprop("sim/hitches/" ~ device ~ "/open", "true");
var hitchname = getprop("sim/hitches/" ~ device ~ "/force_name_jsbsim");
setprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/magnitude", 0.);
setprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/x", 0.);
setprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/y", 0.);
setprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/z", 0.);
if ( device == "aerotow" ) {
setprop("sim/hitches/aerotow/tow/end-force-x", 0.); # MP tow-end forces
setprop("sim/hitches/aerotow/tow/end-force-y", 0.); #
setprop("sim/hitches/aerotow/tow/end-force-z", 0.); #
} else {
# Put automatic winch driver in after-tow phase
phase = 5;
}
} # End function releaseHitch
# ######################################################################################################################
# remove/delete towrope
# ######################################################################################################################
var removeTowrope = func (device){
# remove the towrope from the property tree ai/models
# remove the towrope from the property tree models/
if ( getprop("sim/hitches/" ~ device ~ "/rope/exist") == 1 ) { # does the towrope exist?
# remove 3d model from scenery
# identification is /models/model[x] with x=id_model
var id_model = getprop("sim/hitches/" ~ device ~ "/rope/model_id");
var modelsNode = "models/model[" ~ id_model ~ "]";
props.globals.getNode(modelsNode).remove();
props.globals.getNode("ai/models/" ~ device ~ "rope").remove();
#print("towrope removed");
setprop("sim/hitches/" ~ device ~ "/rope/exist", 0);
}
}
# ######################################################################################################################
# pull in towrope after hitch has been opened
# ######################################################################################################################
var sink_rate_mps = 0.0;
var pull_in_rope = func {
if ( winch_hash.open.getBoolValue() ) {
var current_tow_length = winch_hash.tow.length.getDoubleValue();
if ( current_tow_length > 15.0 ) {
var dt = delta_time.getDoubleValue();
# get position of rope end (former myHitch_pos)
var tow_lat = winch_hash.rope.lat.getDoubleValue();
var tow_lon = winch_hash.rope.lon.getDoubleValue();
var tow_alt_m = winch_hash.rope.alt.getDoubleValue() * FT2M;
# get pitch and heading of rope
var tow_heading_deg = winch_hash.rope.hdg.getDoubleValue();
var tow_pitch_rad = winch_hash.rope.pitch.getDoubleValue() * D2R;
var aiTow_pos = geo.Coord.set_latlon( tow_lat, tow_lon, tow_alt_m );
var delta_length_m = config.winch.max_spool_speed.getDoubleValue() * dt;
var delta_distance_m = delta_length_m * math.cos(tow_pitch_rad);
aiTow_pos = aiTow_pos.apply_course_distance( tow_heading_deg , delta_distance_m );
# Wind influence
aiTow_pos = aiTow_pos.apply_course_distance( wind_from.getDoubleValue() - 180, wind_speed_kt.getDoubleValue() * KT2MPS * dt );
var elevation = geo.elevation( aiTow_pos.lat(), aiTow_pos.lon() );
if( elevation == nil ) elevation = tow_alt_m;
if( tow_alt_m > elevation ){
var delta_alt_m = delta_length_m * math.sin(tow_pitch_rad);
# Calculate vertical sink rate
# assume: drag is exclusively due to the chute, chute size is about 3.14m2 (circle radius 1m), rho is static at 1.29kg/m3
var mass_kg = current_tow_length * config.winch.weight_per_m_kg_m.getDoubleValue();
sink_rate_mps = sink_rate_mps + dt * ( 9.81 - 1.29 * math.pow( sink_rate_mps, 2 ) * 3.14 / mass_kg );
delta_alt_m = delta_alt_m - sink_rate_mps * dt;
aiTow_pos.set_alt( tow_alt_m + delta_alt_m );
} else {
aiTow_pos.set_alt( elevation );
}
#print("aiTow_pos.alt()= ",aiTow_pos.alt()," ",tow_alt_m + delta_alt_m);
# Winch position
var winch_global_pos_x = winch_hash.global_pos[0].getDoubleValue();
var winch_global_pos_y = winch_hash.global_pos[1].getDoubleValue();
var winch_global_pos_z = winch_hash.global_pos[2].getDoubleValue();
var winch_geod = carttogeod(winch_global_pos_x, winch_global_pos_y, winch_global_pos_z);
var aiWinch_pos = geo.Coord.new();
aiWinch_pos.set_latlon( winch_geod[0], winch_geod[1], winch_geod[2] );
var new_tow_length_m = aiTow_pos.direct_distance_to(aiWinch_pos);
var aiHitchpitchto = -math.asin( (aiTow_pos.alt()-aiWinch_pos.alt()) / new_tow_length_m ) * R2D;
# update position of rope
#print("pull in active");
winch_hash.rope.lat.setDoubleValue( aiTow_pos.lat() );
winch_hash.rope.lon.setDoubleValue( aiTow_pos.lon() );
winch_hash.rope.alt.setDoubleValue( aiTow_pos.alt() * M2FT );
# update orientation of rope
winch_hash.rope.hdg.setDoubleValue( aiTow_pos.course_to( aiWinch_pos ) );
winch_hash.rope.pitch.setDoubleValue( aiHitchpitchto );
# update length of rope
winch_hash.tow.length.setDoubleValue( new_tow_length_m );
winch_hash.tow.dist.setDoubleValue( new_tow_length_m );
settimer( pull_in_rope , 0 );
} # end towlength > min
else {
#print("pull in finished!");
winch_hash.actual_spool_speed.setDoubleValue( 0 );
removeTowrope("winch"); # remove towrope model
}
} # end if open
}
# ######################################################################################################################
# set some AI-object default values
# ######################################################################################################################
var setAIObjectDefaults = func (){
# set some default variables, needed to identify, if the found object is an AI-object, a "non-interactiv MP-object or
# an interactive MP-object
var aiNodeID = aerotow_hash.tow.conn_ai_or_mp_id.getIntValue(); # id of former found ai/mp aircraft
aiobjects = props.globals.getNode("ai/models").getChildren();
foreach (var aimember; aiobjects) {
if ( (var c = aimember.getNode("id") ) != nil ) {
var testprop = c.getValue();
if ( testprop == aiNodeID) {
# Set some dummy values. In case of an "interactive"-MP plane
# the correct values will be transmitted in the following loop.
# Create this variables if not present.
aimember.getNode("sim/hitches/aerotow/local-pos-x",1).setValue(-5.);
aimember.getNode("sim/hitches/aerotow/local-pos-y",1).setValue(0.);
aimember.getNode("sim/hitches/aerotow/local-pos-z",1).setValue(0.);
aimember.getNode("sim/hitches/aerotow/tow/dist",1).setValue(-1.);
}
}
}
}
# ######################################################################################################################
# place winch model
# ######################################################################################################################
var setWinchPositionAuto = func {
# remove already existing winch model
if ( getprop("/sim/hitches/winch/winch/winch-model-index") != nil ) {
var id_model = getprop("/sim/hitches/winch/winch/winch-model-index");
var modelsNode = "models/model[" ~ id_model ~ "]";
props.globals.getNode(modelsNode).remove();
#print("winch model removed");
}
var initial_length_m = config.winch.initial_tow_length.getDoubleValue();
var ac_pos = geo.aircraft_position(); # get position of aircraft
var ac_hd = orientation[0].getDoubleValue(); # get heading of aircraft
# setup winch
# get initial runway position
var ipos_lat_deg = getprop("sim/presets/latitude-deg");
var ipos_lon_deg = getprop("sim/presets/longitude-deg");
var ipos_hd_deg = getprop("sim/presets/heading-deg");
var ipos_alt_m = geo.elevation(ipos_lat_deg,ipos_lon_deg);
var ipos_geo = geo.Coord.new().set_latlon(ipos_lat_deg, ipos_lon_deg, ipos_alt_m);
# offset to initial position
var deviation = (ac_pos.distance_to(ipos_geo));
# if deviation is too much, locate winch in front of glider, otherwise locate winch to end of runway
if ( deviation > 200) {
var w = ac_pos.apply_course_distance( ac_hd , initial_length_m -1. );
}
else {
var w = ipos_geo.apply_course_distance( ipos_hd_deg , initial_length_m - 1. );
}
var wpalt = geo.elevation(w.lat(), w.lon());
w.set_alt(wpalt);
var winchModel = geo.put_model("Models/Airport/supacat_winch.xml", w.lat(), w.lon(), (w.alt()+0.81), (w.course_to(ac_pos) ));
winch_hash.global_pos[0].setDoubleValue( w.x() );
winch_hash.global_pos[1].setDoubleValue( w.y() );
winch_hash.global_pos[2].setDoubleValue( w.z() );
winch_hash.tow.dist.setDoubleValue( initial_length_m - 1.0 );
winch_hash.tow.length.setDoubleValue( initial_length_m );
#print("name=",winchModel.getName()," Index=",winchModel.getIndex()," Type=",winchModel.getType() );
#print("val=",winchModel.getValue()," children=",winchModel.getChildren()," size=",size(winchModel) );
setprop("/sim/hitches/winch/winch/winch-model-index",winchModel.getIndex() );
winch_hash.rope_breakage = winch_hash.rope_breakage_p.getBoolValue();
if( winch_hash.rope_breakage ){
if( config.winch.rope_breakage.random.getBoolValue() ){
winch_hash.rope_breakage_height_int = 1 + rand() * 300;
} else {
winch_hash.rope_breakage_height_int = config.winch.rope_breakage.height.getDoubleValue();
}
}
winch_hash.loss_of_power.enabled = winch_hash.loss_of_power.enabled_p.getBoolValue();
if( winch_hash.loss_of_power.enabled ){
if( config.winch.loss_of_power.random.getBoolValue() ){
winch_hash.loss_of_power.height_int = 1 + rand() * 300;
} else {
winch_hash.loss_of_power.height_int = config.winch.loss_of_power.height.getDoubleValue();
}
}
write_message( "pilot", "Connected to winch!" );
winch_hash.open.setBoolValue( 0 );
} # End function setWinchPositionAuto
# ######################################################################################################################
# clutch / un-clutch winch
# ######################################################################################################################
var runWinch = func {
if ( !winch_hash.clutched.getBoolValue() ) {
winch_hash.clutched.setBoolValue( 1 );
write_message( "pilot", "Winch clutched!" );
if( alt_agl_ft.getDoubleValue() < 10.0 ){
auto_winch_driver_init();
auto_winch_driver_update.restart( 0.0 );
}
} else {
winch_hash.clutched.setBoolValue( 0 );
write_message( "pilot", "Winch un-clutched!" );
}
} # End function runWinch
# ######################################################################################################################
# release winch
# ######################################################################################################################
var releaseWinch = func {
winch_hash.open.setBoolValue( 1 );
} # End function releaseWinch
# ######################################################################################################################
# assignHitchLocations
# ######################################################################################################################
var assignHitchLocations = func (device){
if ( fdm == "yasim" ) return; # bypass this routine for Yasim-aircraft
if ( getprop("sim/hitches/" ~ device ~ "/decoupled-force-and-rope-locations") ) return; # bypass this routine
#print("assignHitchLocations");
var hitchname = getprop("sim/hitches/" ~ device ~ "/force_name_jsbsim");
# location-x(yz)-in: JSBSim Structural Frame: x points to tail, y points to right wing, z points upward
# local-pos-x(yz): YaSim frame: x points to nose, y points to left wing, z points upward
setprop("sim/hitches/" ~ device ~ "/local-pos-x", - getprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/location-x-in") * IN2M );
setprop("sim/hitches/" ~ device ~ "/local-pos-y", - getprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/location-y-in") * IN2M );
setprop("sim/hitches/" ~ device ~ "/local-pos-z", getprop("fdm/jsbsim/external_reactions/" ~ hitchname ~ "/location-z-in") * IN2M );
} # End function assignHitchLocations
# ######################################################################################################################
# point transformation
# ######################################################################################################################
var PointRotate3D = func (x,y,z,xr,yr,zr,alpha_deg,beta_deg,gamma_deg){
# ---------------------------------------------------------------------------------
# rotates point (x,y,z) about all 3 cartesian axis
# center of rotation (xr,yr,zr)
# angle of rotation about x-axis = alpha
# angle of rotation about y-axis = beta
# angle of rotation about z-axis = gamma
# delivers new point coordinates (x_new,y_new,z_new)
# ---------------------------------------------------------------------------------
#
#
# Definitions:
# ----------------
#
# x y z
# alpha beta gamma
#
#
# z
# | y
# | /
# |/
# ----->x
#
#----------------------------------------------------------------------------------
# Transformation in rotation-system X_rel = X-Xr = (x-xr, y-yr, z-zr)
var x_rel = x-xr;
var y_rel = y-yr;
var z_rel = z-zr;
# Trigonometry
var alpha_rad = D2R * alpha_deg;
var beta_rad = D2R * beta_deg;
var gamma_rad = D2R * gamma_deg;
var sin_alpha = math.sin(alpha_rad);
var cos_alpha = math.cos(alpha_rad);
var sin_beta = math.sin(beta_rad);
var cos_beta = math.cos(beta_rad);
var sin_gamma = math.sin(gamma_rad);
var cos_gamma = math.cos(gamma_rad);
# Matrices
#
# Rotate about x-axis Rx(alpha)
#
# Rx11 Rx12 Rx13 1 0 0
# Rx(alpha)= Rx21 Rx22 Rx23 = 0 cos(alpha) -sin(alpha)
# Rx31 Rx32 Rx33 0 sin(alpha) cos(alpha)
#
var Rx11 = 1.;
var Rx12 = 0.;
var Rx13 = 0.;
var Rx21 = 0.;
var Rx22 = cos_alpha;
var Rx23 = - sin_alpha;
var Rx31 = 0.;
var Rx32 = sin_alpha;
var Rx33 = cos_alpha;
#
# Rotate about y-axis Ry(beta)
#
# Ry11 Ry12 Ry13 cos(beta) 0 sin(beta)
# Ry(beta)= Ry21 Ry22 Ry23 = 0 1 0
# Ry31 Ry32 Ry33 -sin(beta) 0 cos(beta)
#
var Ry11 = cos_beta;
var Ry12 = 0.;
var Ry13 = sin_beta;
var Ry21 = 0.;
var Ry22 = 1.;
var Ry23 = 0.;
var Ry31 = - sin_beta;
var Ry32 = 0.;
var Ry33 = cos_beta;
#
# Rotate about z-axis Rz(gamma)
#
# Rz11 Rz12 Rz13 cos(gamma) -sin(gamma) 0
# Rz(gamma)= Rz21 Rz22 Rz23 = sin(gamma) cos(gamma) 0
# Rz31 Rz32 Rz33 0 0 1
#
var Rz11 = cos_gamma;
var Rz12 = - sin_gamma;
var Rz13 = 0.;
var Rz21 = sin_gamma;
var Rz22 = cos_gamma;
var Rz23 = 0.;
var Rz31 = 0.;
var Rz32 = 0.;
var Rz33 = 1.;
#
# First rotation about x-axis
# X_x = Rx*X_rel
var x_x = Rx11 * x_rel + Rx12 * y_rel + Rx13 * z_rel;
var y_x = Rx21 * x_rel + Rx22 * y_rel + Rx23 * z_rel;
var z_x = Rx31 * x_rel + Rx32 * y_rel + Rx33 * z_rel;
#
# subsequent rotation about y-axis
# X_xy = Ry*X_x
var x_xy = Ry11 * x_x + Ry12 * y_x + Ry13 * z_x;
var y_xy = Ry21 * x_x + Ry22 * y_x + Ry23 * z_x;
var z_xy = Ry31 * x_x + Ry32 * y_x + Ry33 * z_x;
#
# subsequent rotation about z-axis:
# X_xyz = Rz*X_xy
var x_xyz = Rz11 * x_xy + Rz12 * y_xy + Rz13 * z_xy;
var y_xyz = Rz21 * x_xy + Rz22 * y_xy + Rz23 * z_xy;
var z_xyz = Rz31 * x_xy + Rz32 * y_xy + Rz33 * z_xy;
# Back transformation X_rel = X-Xr = (x-xr, y-yr, z-zr)
var xn = xr + x_xyz;
var yn = yr + y_xyz;
var zn = zr + z_xyz;
var Xn = [xn,yn,zn];
return Xn;
}
var groundspeed_kt = props.globals.getNode("velocities/groundspeed-kt");
var alt_agl_ft = props.globals.getNode("position/altitude-agl-ft");
var weight_lb = nil;
var empty_weight_lb = nil;
var ls = setlistener("/sim/signals/fdm-initialized", func() {
if( fdm == "jsb" ) {
weight_lb = props.globals.getNode("fdm/jsbsim/inertia/weight-lbs");
empty_weight_lb = props.globals.getNode("fdm/jsbsim/inertia/empty-weight-lbs");
} else if ( fdm == "yasim" ) {
weight_lb = props.globals.getNode("yasim/gross-weight-lbs");
}
check_aircraft_tow_settings();
removelistener(ls);
});
var winch_target = 0.8;
var winch_throttle = 0.0;
var winch_idle_force = 1000; # N
var winch_idle_speed = 4; # mps
var acceleration_N_s = nil;
var acceleration_m_s_s = nil;
var phase = 0;
var alpha = 0.0;
var requested_change = 0.0; # positive: request to speed up, negative: request to slow down, 0: auto speed
###########
## AUTOMATIC WINCH DRIVER
###########
# Phase Action description Triggered by
# 0 slowly pull rope until tight (currently omitted) runWinch()
# 1 winch running on idle until aircraft is moving "rope tight" command
# 2 winch accelerating to pre-defined RPM "ready" command
# 3 glider in air, winch adjusts RPM based on angular velocity and faster/slower commands by pilot "clear of ground" command
# 4 winch slowly decelerates to idle aircraft 10-20 deg from automatic release angle
# 5 winch pulls in remaining rope "rope released" command
## Calculation of predefined targets:
# -weight factor, accounts for additional weight (second pilot, water ballast)
# -wind factors, account for different tow speed due to headwind/tailwind and crosswind
var target_force = 0.0; # N
var target_speed = 0.0; # m/s
var auto_winch_driver_init = func () {
target_force = aircraft_settings.winch.typical_tow_force.getDoubleValue();
target_speed = aircraft_settings.winch.typical_tow_speed.getDoubleValue() / 3.6; # convert kph to mps
# Adjust target speed and force for current wind (increase for tail- and crosswind, reduce for headwind)
# define wind frame: expect from 15 kts tailwind to 15 kts headwind -> translates to a change of +- 0.1
var launch_heading_deg = winch_hash.rope.hdg.getDoubleValue();
var wind_from_heading = wind_from.getDoubleValue();
var wind_speed = wind_speed_kt.getDoubleValue();
# calculate linear component
var rel_wind_heading = launch_heading_deg - wind_from_heading;
var wind_linear = wind_speed * math.cos( rel_wind_heading * D2R ); # negative values mean tailwind
var wind_normal = wind_speed * math.cos( -( 90 - rel_wind_heading ) * D2R ); # negative values mean wind from the left
# compensate for tail/headwind
var wind_linear_add = math.clamp( -wind_linear / 150, -0.1, 0.1 );
# compensate for crosswind
var wind_normal_add = math.min( math.abs( wind_normal ) / 150, 0.1 );
# apply compensation to winch driver variables
target_force += ( wind_linear_add + wind_normal_add ) * config.winch.max_force.getDoubleValue();
target_speed += ( wind_linear_add + wind_normal_add ) * config.winch.max_spool_speed.getDoubleValue();
# set acceleration/deceleration variables
acceleration_N_s = config.winch.force_acceleration.getDoubleValue();
acceleration_m_s_s = config.winch.spool_acceleration.getDoubleValue();
# put winch driver into initial state
phase = 0;
}
var my_pos = nil;
var winch_pos = geo.Coord.new();
var auto_winch_driver = func () {
if( !winch_hash.clutched.getBoolValue() ) return;
var dt = delta_time.getDoubleValue();
if( winch_hash.type != 0 and winch_hash.type != 1 ) {
die("(towing system): FATAL: unsupported winch type!");
}
if( phase == 3 or phase == 4 ){
my_pos = geo.aircraft_position();
# Winch position
var winch_global_pos_x = winch_hash.global_pos[0].getDoubleValue();
var winch_global_pos_y = winch_hash.global_pos[1].getDoubleValue();
var winch_global_pos_z = winch_hash.global_pos[2].getDoubleValue();
var winch_geod = carttogeod(winch_global_pos_x, winch_global_pos_y, winch_global_pos_z);
winch_pos.set_latlon( winch_geod[0], winch_geod[1], winch_geod[2] );
alpha = math.asin( ( my_pos.alt() - winch_pos.alt() ) / winch_pos.direct_distance_to( my_pos ) ) * R2D;
}
if( phase == 0 ) {
phase = 1;
}
if( phase == 1 ) {
if( winch_hash.type == 0 ){
force_setting = winch_idle_force;
} else {
speed_setting = winch_idle_speed;
}
# Condition for going into the next phase
if( groundspeed_kt.getDoubleValue() > 1.0 ) {
phase = 2;
write_message( "launch-signaller", "Ready!" );
write_message( "winch-driver", "Ready");
}
} else if ( phase == 2 or phase == 2.5 ){
# Accelerate to target value
if( winch_hash.type == 0 ){
if( force_setting < target_force ){
force_setting += acceleration_N_s * dt;
} else {
force_setting = target_force;
if( phase == 2.5 ){
phase = 3;
}
}
} else {
if( speed_setting < target_speed ){
speed_setting += acceleration_m_s_s * dt;
} else {
speed_setting = target_speed;
if( phase == 2.5 ){
phase = 3;
}
}
}
# Condition for going into the next phase
if( alt_agl_ft.getDoubleValue() > 10 and phase < 2.5 ){
phase = 2.5; # stay here until we have reached the target value
write_message( "launch-signaller", "Clear of ground!" );
write_message( "winch-driver", "Clear");
}
} else if ( phase == 3 ){
if( requested_change != 0 ){
if( winch_hash.type == 0 ){
force_setting += requested_change * config.winch.max_force.getDoubleValue() * dt;
} else {
speed_setting += requested_change * config.winch.max_spool_speed.getDoubleValue() * dt;
}
requested_change -= requested_change * dt;
if( math.abs( requested_change ) < dt ){
requested_change = 0.0;
}
}
var thr_speed = 0.8;
var thr_angle = 0.5;
if( winch_hash.type == 0 ){
var frac_speed = math.clamp( 0.2 + 1 - ( 1 / ( 1 - thr_speed ) ) * ( ( winch_hash.actual_spool_speed.getDoubleValue() / config.winch.max_spool_speed.getDoubleValue() ) - thr_speed ) * 0.8, 0.0, 1.0 );
var frac_angle = math.clamp( 0.2 + 1 - ( 1 / ( 1 - thr_angle ) ) * ( ( alpha / ( aircraft_settings.winch.automatic_release_angle.getDoubleValue() - 30 ) ) - thr_angle ) * 0.8, 0.0, 1.0 );
force_setting = target_force * frac_speed * frac_angle;
} elsif( winch_hash.type == 1 ){
var frac_speed = math.clamp( 0.2 + 1 - ( 1 / ( 1 - thr_speed ) ) * ( ( winch_hash.actual_spool_speed.getDoubleValue() / config.winch.max_spool_speed.getDoubleValue() ) - thr_speed ) * 0.8, 0.0, 1.0 );
var frac_angle = math.clamp( 0.2 + 1 - ( 1 / ( 1 - thr_angle ) ) * ( ( alpha / ( aircraft_settings.winch.automatic_release_angle.getDoubleValue() - 30 ) ) - thr_angle ) * 0.8, 0.0, 1.0 );
speed_setting = target_speed * frac_speed * frac_angle;
}
if( ( alpha >= ( aircraft_settings.winch.automatic_release_angle.getDoubleValue() - 10 ) ) ) {
phase = 4;
}
} else if ( phase == 4 ){
# Reduce values back to idle
if( speed_setting > winch_idle_speed ){
var deceleration = 3 * math.clamp( ( speed_setting - winch_idle_speed ) / ( aircraft_settings.winch.automatic_release_angle.getDoubleValue() - 2 - alpha ), 5, acceleration_m_s_s );
speed_setting -= deceleration * dt;
} else {
speed_setting = winch_idle_speed;
}
# Condition for going into the next phase is set by the releaseHitch() command
} else if ( phase == 5 ){
if( winch_hash.type == 0 ){
force_setting = winch_idle_force;
} else {
speed_setting = winch_idle_speed;
}
auto_winch_driver_update.stop();
}
}
var auto_winch_driver_update = maketimer( 0.0, auto_winch_driver );
auto_winch_driver_update.simulatedTime = 1;
var winch_faster = func ( d = 0.1 ) {
write_message( "pilot", "Faster!");
# Skip acceleration to pre-set target force/speed in case of pilot command
if( phase == 2.5 ){
phase = 3;
}
if( phase != 3 ){
write_message( "winch-driver", "Unable!");
return;
}
if( ( winch_hash.type == 0 and ( force_setting + d * config.winch.max_force.getDoubleValue() ) <= config.winch.max_force.getDoubleValue() ) or
( winch_hash.type == 1 and ( speed_setting + d * config.winch.max_spool_speed.getDoubleValue() ) <= config.winch.max_spool_speed.getDoubleValue() ) ){
requested_change = d;
write_message( "winch-driver", "Faster");
} else {
if( winch_hash.type == 0 ){
force_setting = config.winch.max_force.getDoubleValue();
} else {
speed_setting = config.winch.max_spool_speed.getDoubleValue();
}
write_message( "winch-driver", "At maximum!");
}
}
var winch_slower = func ( d = 0.1 ) {
write_message( "pilot", "Slower!");
# Skip acceleration to pre-set target force/speed in case of pilot command
if( phase == 2.5 ){
phase = 3;
}
if( phase != 3 ){
write_message( "winch-driver", "Unable!");
return;
}
if( ( winch_hash.type == 0 and force_setting - d * config.winch.max_force.getDoubleValue() >= winch_idle_force ) or
( winch_hash.type == 1 and speed_setting - d * config.winch.max_spool_speed.getDoubleValue() >= winch_idle_speed ) ){
requested_change = -d;
write_message( "winch-driver", "Slower");
} else {
if( winch_hash.type == 0 ){
force_setting = winch_idle_force;
} else {
speed_setting = winch_idle_speed;
}
write_message( "winch-driver", "At minimum!");
}
}
##################################################################################################################################
# todo:
# ------
#
# - animate rope slack
# - dynamic ID for ai-rope-model
#
# Please contact D_NXKT at yahoo.de for bug-reports, suggestions, ...
#