LCOV - code coverage report
Current view: top level - SITL - SIM_Glider.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 211 212 99.5 %
Date: 2026-09-09 11:02:08 Functions: 7 7 100.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : /*
      16             :   glider model for high altitude balloon drop
      17             : 
      18             :   controls:
      19             :     - servo6: balloon lift, 1000 for no lift, 2000 for maximum lift
      20             :     - servo10: balloon cut, this cuts away the balloon when high
      21             : 
      22             :   Note that the glider starts off in a lifted by tail pose, with pitch
      23             :   -80 degrees. The balloon then lifts the glider above the ground. The
      24             :   balloon automatically bursts at a height of SIM_GLD_BLN_BRST meters,
      25             :   or can be cut away early with servo10.
      26             : 
      27             :   The maximum rate of the balloon lift is in SIM_GLD_BLN_RATE, in m/s
      28             : 
      29             :   To perform a takeoff first arm on the ground then use
      30             :     servo set 6 2000
      31             :   to release the ground hold. Use this to cut away the balloon:
      32             :     servo set 10 2000
      33             : 
      34             :   For an automatic mission, NAV_ALTITUDE_WAIT should be used to wait
      35             :   for a desired altitude under balloon lift. Then a DO_SET_SERVO with
      36             :   servo 10 and a value of 2000 to cut away the balloon.
      37             :  */
      38             : 
      39             : #include "SIM_Glider.h"
      40             : 
      41             : #if AP_SIM_GLIDER_ENABLED
      42             : 
      43             : #include <stdio.h>
      44             : #include <AP_Logger/AP_Logger.h>
      45             : #include <GCS_MAVLink/GCS.h>
      46             : 
      47             : extern const AP_HAL::HAL& hal;
      48             : 
      49             : using namespace SITL;
      50             : 
      51             : // SITL glider parameters
      52             : const AP_Param::GroupInfo Glider::var_info[] = {
      53             :     // @Param: BLN_BRST
      54             :     // @DisplayName: balloon burst height
      55             :     // @Description: balloon burst height
      56             :     // @Units: m
      57             :     AP_GROUPINFO("BLN_BRST", 1, Glider, balloon_burst_amsl, 30000),
      58             : 
      59             :     // @Param: BLN_RATE
      60             :     // @DisplayName: balloon climb rate
      61             :     // @Description: balloon climb rate. If the value is less than zero then the balloon is disabled.
      62             :     // @Units: m/s
      63             :     AP_GROUPINFO("BLN_RATE",  2, Glider, balloon_rate, 5.5),
      64             : 
      65             :     AP_GROUPEND
      66             : };
      67             : 
      68           1 : Glider::Glider(const char *frame_str) :
      69           1 :     Aircraft(frame_str)
      70             : {
      71           1 :     ground_behavior = GROUND_BEHAVIOR_NO_MOVEMENT;
      72           1 :     carriage_state = carriageState::WAITING_FOR_PICKUP;
      73           1 :     AP::sitl()->models.glider_ptr = this;
      74           1 :     AP_Param::setup_object_defaults(this, var_info);
      75           1 : }
      76             : 
      77             : 
      78             : // Torque calculation function
      79      646440 : Vector3f Glider::getTorque(float inputAileron, float inputElevator, float inputRudder, const Vector3f &force) const
      80             : {
      81             :     // Calculate dynamic pressure
      82      646440 :     const auto &m = model;
      83      646440 :     double qPa = 0.5*air_density*sq(velocity_air_bf.length());
      84      646440 :     const float aileron_rad = inputAileron * radians(m.aileronDeflectionLimitDeg);
      85      646440 :     const float elevator_rad = inputElevator * radians(m.elevatorDeflectionLimitDeg);
      86      646440 :     const float rudder_rad = inputRudder * radians(m.rudderDeflectionLimitDeg);
      87      646440 :     const float tas = MAX(airspeed * eas2tas, 1);
      88             : 
      89      646440 :     float Cl = (m.Cl2 * sq(alpharad) + m.Cl1 * alpharad + m.Cl0) * betarad;
      90      646440 :     float Cm = m.Cm2 * sq(alpharad) + m.Cm1 * alpharad + m.Cm0;
      91      646440 :     float Cn = (m.Cn2 * sq(alpharad) + m.Cn1 * alpharad + m.Cn0) * betarad;
      92             : 
      93      646440 :     Cl += m.deltaClperRadianElev * elevator_rad;
      94      646440 :     Cm += m.deltaCmperRadianElev * elevator_rad;
      95      646440 :     Cn += m.deltaCnperRadianElev * elevator_rad;
      96             : 
      97      646440 :     Cl += m.deltaClperRadianRud * rudder_rad;
      98      646440 :     Cm += m.deltaCmperRadianRud * rudder_rad;
      99      646440 :     Cn += m.deltaCnperRadianRud * rudder_rad;
     100             : 
     101      646440 :     Cl += (m.deltaClperRadianAil2 * sq(alpharad) + m.deltaClperRadianAil1 * alpharad + m.deltaClperRadianAil0) * aileron_rad;
     102      646440 :     Cm += m.deltaCmperRadianAil * aileron_rad;
     103      646440 :     Cn += (m.deltaCnperRadianAil2 * sq(alpharad) + m.deltaCnperRadianAil1 * alpharad + m.deltaCnperRadianAil0) * aileron_rad;
     104             : 
     105             :     // derivatives
     106      646440 :     float Clp = m.Clp2 * sq(alpharad) + m.Clp1 * alpharad + m.Clp0;
     107      646440 :     float Clr = m.Clr2 * sq(alpharad) + m.Clr1 * alpharad + m.Clr0;
     108      646440 :     float Cnp = m.Cnp2 * sq(alpharad) + m.Cnp1 * alpharad + m.Cnp0;
     109      646440 :     float Cnr = m.Cnr2 * sq(alpharad) + m.Cnr1 * alpharad + m.Cnr0;
     110             : 
     111             :     // normalise gyro rates
     112      646440 :     Vector3f pqr_norm = gyro;
     113      646440 :     pqr_norm.x *= 0.5 * m.refSpan / tas;
     114      646440 :     pqr_norm.y *= 0.5 * m.refChord / tas;
     115      646440 :     pqr_norm.z *= 0.5 * m.refSpan / tas;
     116             : 
     117      646440 :     Cl += pqr_norm.x * Clp;
     118      646440 :     Cl += pqr_norm.z * Clr;
     119      646440 :     Cn += pqr_norm.x * Cnp;
     120      646440 :     Cn += pqr_norm.z * Cnr;
     121             : 
     122      646440 :     Cm += pqr_norm.y * m.Cmq;
     123             : 
     124      646440 :     float Mx = Cl * qPa * m.Sref * m.refSpan;
     125      646440 :     float My = Cm * qPa * m.Sref * m.refChord;
     126      646440 :     float Mz = Cn * qPa * m.Sref * m.refSpan;
     127             : 
     128             : 
     129             : #if 0
     130             :     // @LoggerMessage: GLT
     131             :     // @Description: Simulated Glider Angles and coefficients
     132             :     // @Field: TimeUS: Time since system startup
     133             :     // @Field: Alpha: alpha angle
     134             :     // @Field: Beta: beta angle
     135             :     // @Field: Cl: lift coefficent
     136             :     // @Field: Cm: roll coffecient
     137             :     // @Field: Cn: yaw coefficient
     138             :     AP::logger().Write("GLT", "TimeUS,Alpha,Beta,Cl,Cm,Cn", "Qfffff",
     139             :                        AP_HAL::micros64(),
     140             :                        degrees(alpharad),
     141             :                        degrees(betarad),
     142             :                        Cl, Cm, Cn);
     143             : #endif
     144             : 
     145      646440 :     return Vector3f(Mx/m.IXX, My/m.IYY, Mz/m.IZZ);
     146             : }
     147             : 
     148             : // Force calculation, return vector in Newtons
     149      646440 : Vector3f Glider::getForce(float inputAileron, float inputElevator, float inputRudder)
     150             : {
     151      646440 :     const auto &m = model;
     152      646440 :     const float aileron_rad = inputAileron * radians(m.aileronDeflectionLimitDeg);
     153      646440 :     const float elevator_rad = inputElevator * radians(m.elevatorDeflectionLimitDeg);
     154      646440 :     const float rudder_rad = inputRudder * radians(m.rudderDeflectionLimitDeg);
     155             : 
     156             :     // dynamic pressure
     157      646440 :     double qPa = 0.5*air_density*sq(velocity_air_bf.length());
     158             : 
     159      646440 :     float CA = m.CA2 * sq(alpharad) + m.CA1 * alpharad + m.CA0;
     160      646440 :     float CY = (m.CY2 * sq(alpharad) + m.CY1 * alpharad + m.CY0) * betarad;
     161      646440 :     float CN = m.CN2 * sq(alpharad) + m.CN1 * alpharad + m.CN0;
     162             : 
     163      646440 :     CN += m.deltaCNperRadianElev * elevator_rad;
     164      646440 :     CA += m.deltaCAperRadianElev * elevator_rad;
     165      646440 :     CY += m.deltaCYperRadianElev * elevator_rad;
     166             : 
     167      646440 :     CN += m.deltaCNperRadianRud * rudder_rad;
     168      646440 :     CA += m.deltaCAperRadianRud * rudder_rad;
     169      646440 :     CY += m.deltaCYperRadianRud * rudder_rad;
     170             : 
     171      646440 :     CN += m.deltaCNperRadianAil * aileron_rad;
     172      646440 :     CA += m.deltaCAperRadianAil * aileron_rad;
     173      646440 :     CY += m.deltaCYperRadianAil * aileron_rad;
     174             :     
     175      646440 :     float Fx = -CA * qPa * m.Sref;
     176      646440 :     float Fy =  CY * qPa * m.Sref;
     177      646440 :     float Fz = -CN * qPa * m.Sref;
     178             : 
     179      646440 :     Vector3f ret = Vector3f(Fx, Fy, Fz);
     180             : 
     181      646440 :     float Flift = Fx * sin(alpharad)  - Fz * cos(alpharad);
     182      646440 :     float Fdrag = -Fx * cos(alpharad) - Fz * sin(alpharad);
     183             : 
     184      646440 :     if (carriage_state == carriageState::RELEASED) {
     185      585249 :         uint32_t now = AP_HAL::millis();
     186      585249 :         sim_LD = 0.1 * constrain_float(Flift/MAX(1.0e-6,Fdrag),0,20) + 0.9 * sim_LD;
     187      585249 :         if (now - last_drag_ms > 10 &&
     188       71834 :             airspeed > 1) {
     189       42065 :             last_drag_ms = now;
     190             : #if HAL_LOGGING_ENABLED
     191             :             // @LoggerMessage: SLD
     192             :             // @Description: Simulated Glider Dropped Calculations
     193             :             // @Field: TimeUS: Time since system startup
     194             :             // @Field: AltFt: altitude in feet
     195             :             // @Field: AltM: altitude in metres
     196             :             // @Field: EAS: equivalent airspeed
     197             :             // @Field: TAS: true airspeed
     198             :             // @Field: AD: air density
     199             :             // @Field: Fl: lift
     200             :             // @Field: Fd: drag
     201             :             // @Field: LD: lift/drag ratio
     202             :             // @Field: Elev: elevator output
     203             :             // @Field: AoA: angle of attack
     204             :             // @Field: Fx: X-axis force
     205             :             // @Field: Fy: Y-axis force
     206             :             // @Field: Fz: Z-axis force
     207             :             // @Field: q: air pressure
     208       84130 :             AP::logger().Write("SLD", "TimeUS,AltFt,AltM,EAS,TAS,AD,Fl,Fd,LD,Elev,AoA,Fx,Fy,Fz,q", "Qffffffffffffff",
     209             :                                AP_HAL::micros64(),
     210       42065 :                                (location.alt*0.01)/FEET_TO_METERS,
     211       42065 :                                location.alt*0.01,
     212       42065 :                                velocity_air_bf.length()/eas2tas,
     213       42065 :                                velocity_air_bf.length(),
     214       42065 :                                air_density,
     215       42065 :                                Flift, Fdrag, sim_LD,
     216       42065 :                                degrees(elevator_rad),
     217       42065 :                                degrees(alpharad),
     218             :                                Fx, Fy, Fz,
     219             :                                qPa);
     220             :             // @LoggerMessage: SL2
     221             :             // @Description: More Simulated Glider Dropped Calculations
     222             :             // @Field: TimeUS: Time since system startup
     223             :             // @Field: AltFt: altitude in feet
     224             :             // @Field: KEAS: equivalent airspeed in knots
     225             :             // @Field: KTAS: true airspeed in knots
     226             :             // @Field: AD: air density
     227             :             // @Field: Fl: lift
     228             :             // @Field: Fd: drag
     229             :             // @Field: LD: lift/drag ratio
     230             :             // @Field: Elev: elevator output
     231             :             // @Field: Ail: aileron output
     232             :             // @Field: Rud: rudder output
     233             :             // @Field: AoA: Angle of Attack
     234             :             // @Field: SSA: Side Slip Angle
     235             :             // @Field: q: air pressire
     236             :             // @Field: Az: z-axis body-frame acceleration
     237       84130 :             AP::logger().Write("SL2", "TimeUS,AltFt,KEAS,KTAS,AD,Fl,Fd,LD,Elev,Ail,Rud,AoA,SSA,q,Az", "Qffffffffffffff",
     238             :                                AP_HAL::micros64(),
     239       42065 :                                (location.alt*0.01)/FEET_TO_METERS,
     240       42065 :                                M_PER_SEC_TO_KNOTS*velocity_air_bf.length()/eas2tas,
     241       42065 :                                M_PER_SEC_TO_KNOTS*velocity_air_bf.length(),
     242       42065 :                                air_density,
     243       42065 :                                Flift, Fdrag, sim_LD,
     244       42065 :                                degrees(elevator_rad),
     245       42065 :                                degrees(aileron_rad),
     246       42065 :                                degrees(rudder_rad),
     247       42065 :                                degrees(alpharad),
     248       42065 :                                degrees(betarad),
     249             :                                qPa,
     250       42065 :                                accel_body.z);
     251             : 
     252             :             // @LoggerMessage: SCTL
     253             :             // @Description: Simulated Glider Drop control outputs
     254             :             // @Field: TimeUS: Time since system startup
     255             :             // @Field: Ail: aileron output
     256             :             // @Field: Elev: elevator output
     257             :             // @Field: Rudd: rudder output
     258       84130 :             AP::logger().Write("SCTL", "TimeUS,Ail,Elev,Rudd", "Qfff",
     259             :                                AP_HAL::micros64(),
     260       42065 :                                degrees(aileron_rad),
     261       42065 :                                degrees(elevator_rad),
     262       42065 :                                degrees(rudder_rad));
     263             : #endif // HAL_LOGGING_ENABLED
     264             :         }
     265             :     }
     266             : 
     267             : 
     268      646440 :     return ret;
     269             : }
     270             : 
     271     1768371 : void Glider::calculate_forces(const struct sitl_input &input, Vector3f &rot_accel, Vector3f &body_accel)
     272             : {
     273     1768371 :     filtered_servo_setup(1, 1100, 1900, model.aileronDeflectionLimitDeg);
     274     1768371 :     filtered_servo_setup(4, 1100, 1900, model.aileronDeflectionLimitDeg);
     275     1768371 :     filtered_servo_setup(2, 1100, 1900, model.elevatorDeflectionLimitDeg);
     276     1768371 :     filtered_servo_setup(3, 1100, 1900, model.rudderDeflectionLimitDeg);
     277             :     
     278     1768371 :     float aileron  = 0.5*(filtered_servo_angle(input, 1) + filtered_servo_angle(input, 4));
     279     1768371 :     float elevator = filtered_servo_angle(input, 2);
     280     1768371 :     float rudder   = filtered_servo_angle(input, 3);
     281     1768371 :     float balloon  = MAX(0.0f, filtered_servo_range(input, 5)); // Don't let the balloon receive downwards commands.
     282     1768371 :     float balloon_cut = filtered_servo_range(input, 9);
     283             : 
     284             :     // Move balloon upwards using balloon velocity from channel 6
     285             :     // Aircraft is released from ground constraint when channel 6 PWM > 1010
     286             :     // Once released, plane will be dropped when balloon_burst_amsl is reached or channel 6 is set to PWM 1000
     287     1768371 :     if (carriage_state == carriageState::WAITING_FOR_RELEASE) {
     288     1118467 :         balloon_velocity = Vector3f(-wind_ef.x, -wind_ef.y, -wind_ef.z -balloon_rate * balloon);
     289     1118467 :         balloon_position += balloon_velocity * (1.0e-6 * (float)frame_time_us);
     290     1118467 :         const float height_AMSL = 0.01f * (float)home.alt - position.z;
     291             :         // release at burst height or when balloon cut output goes high
     292     2236934 :         if (hal.scheduler->is_system_initialized() &&
     293     1118467 :             (height_AMSL > balloon_burst_amsl || balloon_cut > 0.8)) {
     294           1 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "pre-release at %i m AMSL", (int)height_AMSL);
     295           1 :             carriage_state = carriageState::PRE_RELEASE;
     296             :         }
     297      649904 :     } else if (carriage_state == carriageState::PRE_RELEASE) {
     298             :         // slow down for release
     299        2397 :         balloon_velocity *= 0.999;
     300        2397 :         balloon_position += balloon_velocity * (1.0e-6 * (float)frame_time_us);
     301        2397 :         if (balloon_velocity.length() < 0.5) {
     302           1 :             carriage_state = carriageState::RELEASED;
     303           1 :             use_smoothing = false;
     304           1 :             GCS_SEND_TEXT(MAV_SEVERITY_INFO, "released at %.0f m AMSL", (0.01f * home.alt) - position.z);
     305             :         }
     306      647507 :     } else if (carriage_state == carriageState::WAITING_FOR_PICKUP) {
     307             :         // Don't allow the balloon to drag sideways until the pickup
     308       62259 :         balloon_velocity = Vector3f(0.0f, 0.0f, -balloon_rate * balloon);
     309       62259 :         balloon_position += balloon_velocity * (1.0e-6 * (float)frame_time_us);
     310             :     }
     311             : 
     312             :     // calculate angle of attack
     313     1768371 :     alpharad = atan2f(velocity_air_bf.z, velocity_air_bf.x);
     314     1768371 :     betarad = atan2f(velocity_air_bf.y,velocity_air_bf.x);
     315             : 
     316     1768371 :     alpharad = constrain_float(alpharad, -model.alphaRadMax, model.alphaRadMax);
     317     1768371 :     betarad = constrain_float(betarad, -model.betaRadMax, model.betaRadMax);
     318             : 
     319     1768371 :     Vector3f force;
     320             : 
     321     1768371 :     if (!update_balloon(balloon, force, rot_accel)) {
     322      646440 :         force = getForce(aileron, elevator, rudder);
     323      646440 :         rot_accel = getTorque(aileron, elevator, rudder, force);
     324             :     }
     325             : 
     326     1768371 :     accel_body = force / model.mass;
     327             : 
     328     1768371 :     if (on_ground()) {
     329             :         // add some ground friction
     330       29537 :         Vector3f vel_body = dcm.transposed() * velocity_ef;
     331       29537 :         accel_body.x -= vel_body.x * 0.3f;
     332             :     }
     333             : 
     334             :     // constrain accelerations
     335     1768371 :     accel_body.x = constrain_float(accel_body.x, -16*GRAVITY_MSS, 16*GRAVITY_MSS);
     336     1768371 :     accel_body.y = constrain_float(accel_body.y, -16*GRAVITY_MSS, 16*GRAVITY_MSS);
     337     1768371 :     accel_body.z = constrain_float(accel_body.z, -16*GRAVITY_MSS, 16*GRAVITY_MSS);
     338             : 
     339     1768371 : }
     340             :     
     341             : /*
     342             :   update the plane simulation by one time step
     343             :  */
     344     1768371 : void Glider::update(const struct sitl_input &input)
     345             : {
     346     1768371 :     Vector3f rot_accel;
     347             : 
     348     1768371 :     update_wind(input);
     349             :     
     350     1768371 :     calculate_forces(input, rot_accel, accel_body);
     351             :     
     352     1768371 :     if (carriage_state == carriageState::WAITING_FOR_PICKUP) {
     353             :         // Handle special case where plane is being held nose down waiting to be lifted
     354       62258 :         accel_body = dcm.transposed() * Vector3f(0.0f, 0.0f, -GRAVITY_MSS);
     355       62258 :         velocity_ef.zero();
     356       62258 :         gyro.zero();
     357       62258 :         dcm.from_euler(0.0f, radians(-80.0f), radians(home_yaw));
     358       62258 :         use_smoothing = true;
     359       62258 :         adjust_frame_time(constrain_float(sitl->loop_rate_hz, rate_hz-1, rate_hz+1));
     360             :     } else {
     361     1706113 :         update_dynamics(rot_accel);
     362             :     }
     363     1768371 :     update_external_payload(input);
     364             : 
     365             :     // update lat/lon/altitude
     366     1768371 :     update_position();
     367     1768371 :     time_advance();
     368             : 
     369             :     // update magnetic field
     370     1768371 :     update_mag_field_bf();
     371             : 
     372     1768371 :     update_battery();
     373     1768371 : }
     374             : 
     375             : /*
     376             :    return true if we are on the ground
     377             : */
     378     6886710 : bool Glider::on_ground() const
     379             : {
     380     6886710 :     switch (carriage_state) {
     381     2340996 :     case carriageState::NONE:
     382             :     case carriageState::RELEASED:
     383     2340996 :         return hagl() <= 0.001;
     384     4545714 :     case carriageState::WAITING_FOR_PICKUP:
     385             :     case carriageState::WAITING_FOR_RELEASE:
     386             :     case carriageState::PRE_RELEASE:
     387             :         // prevent bouncing around ground
     388             :         // don't do ground interaction if being carried
     389     4545714 :         break;
     390             :     }
     391     4545714 :     return false;
     392             : }
     393             : 
     394             : /*
     395             :   implement balloon lift
     396             :   controlled by SIM_BLN_BRST and SIM_BLN_RATE
     397             :  */
     398     1768371 : bool Glider::update_balloon(float balloon, Vector3f &force, Vector3f &rot_accel)
     399             : {
     400             :     // by setting a negative balloon rate we disable the balloon
     401     1768371 :     if (balloon_rate < 0) {
     402           0 :         carriage_state = carriageState::RELEASED;
     403             :     }
     404             :     
     405     1768371 :     if (!hal.util->get_soft_armed()) {
     406       63107 :         return false;
     407             :     }
     408             : 
     409     1705264 :     switch (carriage_state) {
     410      583333 :     case carriageState::NONE:
     411             :     case carriageState::RELEASED:
     412             :         // balloon not active
     413      583333 :         disable_origin_movement = false;
     414      583333 :         return false;
     415             : 
     416     1121931 :     case carriageState::WAITING_FOR_PICKUP:
     417             :     case carriageState::WAITING_FOR_RELEASE:
     418             :     case carriageState::PRE_RELEASE:
     419             :         // while under balloon disable origin movement to allow for balloon position calculations
     420     1121931 :         disable_origin_movement = true;
     421     1121931 :         break;
     422             :     }
     423             : 
     424             :     // assume a 50m tether with a 1Hz pogo frequency and damping ratio of 0.2
     425     1121931 :     Vector3f tether_pos_bf{-1.0f,0.0f,0.0f}; // tether attaches to vehicle tail approx 1m behind c.g.
     426     1121931 :     const float omega = model.tetherPogoFreq * M_2PI; // rad/sec
     427     1121931 :     const  float zeta = 0.7f;
     428     1121931 :     float tether_stiffness = model.mass * sq(omega); // N/m
     429     1121931 :     float tether_damping = 2.0f * zeta * omega / model.mass; // N/(m/s)
     430             :     // NED relative position vector from tether attachment on plane to balloon attachment
     431     1121931 :     Vector3f relative_position = balloon_position - (position.tofloat() + (dcm * tether_pos_bf));
     432     1121931 :     const float separation_distance = relative_position.length();
     433             : 
     434             :     // NED unit vector pointing from tether attachment on plane to attachment on balloon
     435     1121931 :     Vector3f tether_unit_vec_ef = relative_position.normalized();
     436             : 
     437             :     // NED velocity of attachment point on plane
     438     1121931 :     Vector3f attachment_velocity_ef = velocity_ef + dcm * (gyro % tether_pos_bf);
     439             : 
     440             :     // NED velocity of attachment point on balloon as seen by observer on attachemnt point on plane
     441     1121931 :     Vector3f relative_velocity = balloon_velocity - attachment_velocity_ef;
     442             : 
     443     1121931 :     float separation_speed = relative_velocity * tether_unit_vec_ef;
     444             : 
     445             :     // rate increase in separation between attachment point on plane and balloon
     446             :     // tension force in tether due to stiffness and damping
     447     1121931 :     float tension_force = MAX(0.0f, (separation_distance - model.tetherLength) * tether_stiffness);
     448     1121931 :     if (tension_force > 0.0f) {
     449     1113304 :         tension_force += constrain_float(separation_speed * tether_damping, 0.0f, 0.05f * tension_force);
     450             :     }
     451             : 
     452     1121931 :     if (carriage_state == carriageState::WAITING_FOR_PICKUP && tension_force > 1.2f * model.mass * GRAVITY_MSS && balloon > 0.01f) {
     453           1 :         carriage_state = carriageState::WAITING_FOR_RELEASE;
     454             :     }
     455             : 
     456     1121931 :     if (carriage_state == carriageState::WAITING_FOR_RELEASE ||
     457        3464 :         carriage_state == carriageState::PRE_RELEASE) {
     458     1120864 :         Vector3f tension_force_vector_ef = tether_unit_vec_ef * tension_force;
     459     1120864 :         Vector3f tension_force_vector_bf = dcm.transposed() * tension_force_vector_ef;
     460     1120864 :         force = tension_force_vector_bf;
     461             : 
     462             :         // drag force due to lateral motion assuming projected area from Y is 20% of projected area seen from Z and
     463             :         // assuming bluff body drag characteristic. In reality we would need an aero model that worked flying backwards,
     464             :         // but this will have to do for now.
     465     1120864 :         Vector3f aero_force_bf = Vector3f(0.0f, 0.2f * velocity_air_bf.y * fabsf(velocity_air_bf.y), velocity_air_bf.z * fabsf(velocity_air_bf.z));
     466     1120864 :         aero_force_bf *= air_density * model.Sref;
     467     1120864 :         force -= aero_force_bf;
     468             : 
     469     1120864 :         Vector3f tension_moment_vector_bf = tether_pos_bf % tension_force_vector_bf;
     470     1120864 :         Vector3f tension_rot_accel = Vector3f(tension_moment_vector_bf.x/model.IXX, tension_moment_vector_bf.y/model.IYY, tension_moment_vector_bf.z/model.IZZ);
     471     1120864 :         rot_accel = tension_rot_accel;
     472             : 
     473             :         // add some rotation damping due to air resistance assuming a 2 sec damping time constant at SL density
     474             :         // TODO model roll damping with more accuracy using Clp data for zero alpha as a first approximation
     475     1120864 :         rot_accel -= gyro * 0.5 * air_density;
     476     1120864 :     } else {
     477             :         // tether is either slack awaiting pickup or released
     478        1067 :         rot_accel.zero();
     479        1067 :         force = dcm.transposed() * Vector3f(0.0f, 0.0f, -GRAVITY_MSS * model.mass);
     480             :     }
     481             : 
     482             :     // balloon is active
     483     1121931 :     return true;
     484             : }
     485             : 
     486             : #endif // AP_SIM_GLIDER_ENABLED

Generated by: LCOV version 1.14