LCOV - code coverage report
Current view: top level - AP_Soaring - AP_Soaring.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 156 179 87.2 %
Date: 2026-09-09 11:02:08 Functions: 18 18 100.0 %

          Line data    Source code
       1             : #include "AP_Soaring.h"
       2             : 
       3             : #if HAL_SOARING_ENABLED
       4             : 
       5             : #include <AP_AHRS/AP_AHRS.h>
       6             : #include <AP_Logger/AP_Logger.h>
       7             : #include <AP_TECS/AP_TECS.h>
       8             : #include <GCS_MAVLink/GCS.h>
       9             : #include <stdint.h>
      10             : 
      11             : #if HAL_SOARING_NVF_EKF_ENABLED
      12             : static constexpr uint32_t NVF_PUBLISHER_DELAY_MS = 250;
      13             : #endif // HAL_SOARING_NVF_EKF_ENABLED
      14             : 
      15             : // ArduSoar parameters
      16             : const AP_Param::GroupInfo SoaringController::var_info[] = {
      17             :     // @Param: ENABLE
      18             :     // @DisplayName: Is the soaring mode enabled or not
      19             :     // @Description: Toggles the soaring mode on and off
      20             :     // @Values: 0:Disable,1:Enable
      21             :     // @User: Advanced
      22             :     AP_GROUPINFO_FLAGS("ENABLE", 1, SoaringController, soar_active, 0, AP_PARAM_FLAG_ENABLE),
      23             : 
      24             :     // @Param: VSPEED
      25             :     // @DisplayName: Vertical v-speed
      26             :     // @Description: Rate of climb to trigger themalling speed
      27             :     // @Units: m/s
      28             :     // @Range: 0 10
      29             :     // @User: Advanced
      30             :     AP_GROUPINFO("VSPEED", 2, SoaringController, thermal_vspeed, 0.7f),
      31             : 
      32             :     // @Param: Q1
      33             :     // @DisplayName: Process noise
      34             :     // @Description: Standard deviation of noise in process for strength
      35             :     // @Range: 0.0001 0.01
      36             :     // @User: Advanced
      37             :     AP_GROUPINFO("Q1", 3, SoaringController, thermal_q1, 0.001f),
      38             : 
      39             :     // @Param: Q2
      40             :     // @DisplayName: Process noise
      41             :     // @Description: Standard deviation of noise in process for position and radius
      42             :     // @Range: 0.01 1
      43             :     // @User: Advanced
      44             :     AP_GROUPINFO("Q2", 4, SoaringController, thermal_q2, 0.03f),
      45             : 
      46             :     // @Param: R
      47             :     // @DisplayName: Measurement noise
      48             :     // @Description: Standard deviation of noise in measurement
      49             :     // @Range: 0.01 1
      50             :     // @User: Advanced
      51             : 
      52             :     AP_GROUPINFO("R", 5, SoaringController, thermal_r, 0.45f),
      53             : 
      54             :     // @Param: DIST_AHEAD
      55             :     // @DisplayName: Distance to thermal center
      56             :     // @Description: Initial guess of the distance to the thermal center
      57             :     // @Units: m
      58             :     // @Range: 0 100
      59             :     // @User: Advanced
      60             :     AP_GROUPINFO("DIST_AHEAD", 6, SoaringController, thermal_distance_ahead, 5.0f),
      61             : 
      62             :     // @Param: MIN_THML_S
      63             :     // @DisplayName: Minimum thermalling time
      64             :     // @Description: Minimum number of seconds to spend thermalling
      65             :     // @Units: s
      66             :     // @Range: 0 600
      67             :     // @User: Advanced
      68             :     AP_GROUPINFO("MIN_THML_S", 7, SoaringController, min_thermal_s, 20),
      69             : 
      70             :     // @Param: MIN_CRSE_S
      71             :     // @DisplayName: Minimum cruising time
      72             :     // @Description: Minimum number of seconds to spend cruising
      73             :     // @Units: s
      74             :     // @Range: 0 600
      75             :     // @User: Advanced
      76             :     AP_GROUPINFO("MIN_CRSE_S", 8, SoaringController, min_cruise_s, 10),
      77             : 
      78             :     // @Param: POLAR_CD0
      79             :     // @DisplayName: Zero lift drag coef.
      80             :     // @Description: Zero lift drag coefficient
      81             :     // @Range: 0.005 0.5
      82             :     // @User: Advanced
      83             :     AP_GROUPINFO("POLAR_CD0", 9, SoaringController, _polarParams.CD0, 0.027),
      84             : 
      85             :     // @Param: POLAR_B
      86             :     // @DisplayName: Induced drag coeffient
      87             :     // @Description: Induced drag coeffient
      88             :     // @Range: 0.005 0.05
      89             :     // @User: Advanced
      90             :     AP_GROUPINFO("POLAR_B", 10, SoaringController, _polarParams.B, 0.031),
      91             : 
      92             :     // @Param: POLAR_K
      93             :     // @DisplayName: Cl factor
      94             :     // @Description: Cl factor 2*m*g/(rho*S)
      95             :     // @Units: m.m/s/s
      96             :     // @Range: 20 400
      97             :     // @User: Advanced
      98             :     AP_GROUPINFO("POLAR_K", 11, SoaringController, _polarParams.K, 25.6),
      99             : 
     100             :     // @Param: ALT_MAX
     101             :     // @DisplayName: Maximum soaring altitude, relative to the home location
     102             :     // @Description: Don't thermal any higher than this.
     103             :     // @Units: m
     104             :     // @Range: 0 5000.0
     105             :     // @User: Advanced
     106             :     AP_GROUPINFO("ALT_MAX", 12, SoaringController, alt_max, 350.0),
     107             : 
     108             :     // @Param: ALT_MIN
     109             :     // @DisplayName: Minimum soaring altitude, relative to the home location
     110             :     // @Description: Don't get any lower than this.
     111             :     // @Units: m
     112             :     // @Range: 0 1000.0
     113             :     // @User: Advanced
     114             :     AP_GROUPINFO("ALT_MIN", 13, SoaringController, alt_min, 50.0),
     115             : 
     116             :     // @Param: ALT_CUTOFF
     117             :     // @DisplayName: Maximum power altitude, relative to the home location
     118             :     // @Description: Cut off throttle at this alt.
     119             :     // @Units: m
     120             :     // @Range: 0 5000.0
     121             :     // @User: Advanced
     122             :     AP_GROUPINFO("ALT_CUTOFF", 14, SoaringController, alt_cutoff, 250.0),
     123             :     
     124             :     // 15 was SOAR_ENABLE_CH, now RCX_OPTION
     125             : 
     126             :     // @Param: MAX_DRIFT
     127             :     // @DisplayName: (Optional) Maximum drift distance to allow when thermalling.
     128             :     // @Description: The previous mode will be restored if the horizontal distance to the thermalling start location exceeds this value. -1 to disable.
     129             :     // @Range: 0 1000
     130             :     // @User: Advanced
     131             :     AP_GROUPINFO("MAX_DRIFT", 16, SoaringController, max_drift, -1),
     132             : 
     133             :     // @Param: MAX_RADIUS
     134             :     // @DisplayName: (Optional) Maximum distance from home
     135             :     // @Description: RTL will be entered when a thermal is exited and the plane is more than this distance from home. -1 to disable.
     136             :     // @Range: 0 1000
     137             :     // @User: Advanced
     138             :     AP_GROUPINFO("MAX_RADIUS", 17, SoaringController, max_radius, -1),
     139             : 
     140             :     // @Param: THML_BANK
     141             :     // @DisplayName: Thermalling bank angle
     142             :     // @Description: This parameter sets the bank angle to use when thermalling. Typically 30 - 45 degrees works well.
     143             :     // @Range: 20 50
     144             :     // @User: Advanced
     145             :     // @Units: deg
     146             :     AP_GROUPINFO("THML_BANK", 18, SoaringController, thermal_bank, 30.0),
     147             : 
     148             :     // 19 reserved for POLAR_LEARN.
     149             : 
     150             :     // @Param: THML_ARSPD
     151             :     // @DisplayName: Specific setting for airspeed when soaring in THERMAL mode.
     152             :     // @Description: If non-zero this airspeed will be used when thermalling. A value of 0 will use AIRSPEED_CRUISE.
     153             :     // @Range: 0 50
     154             :     // @User: Advanced
     155             :     AP_GROUPINFO("THML_ARSPD", 20, SoaringController, soar_thermal_airspeed, 0),
     156             : 
     157             :     // @Param: CRSE_ARSPD
     158             :     // @DisplayName: Specific setting for airspeed when soaring in AUTO mode.
     159             :     // @Description: If non-zero this airspeed will be used when cruising between thermals in AUTO. If set to -1, airspeed will be selected based on speed-to-fly theory. If set to 0, then AIRSPEED_CRUISE will be used while cruising between thermals.
     160             :     // @Range: -1 50
     161             :     // @User: Advanced
     162             :     AP_GROUPINFO("CRSE_ARSPD", 21, SoaringController, soar_cruise_airspeed, 0),
     163             : 
     164             :     // @Param: THML_FLAP
     165             :     // @DisplayName: Flap percent to be used during thermalling flight.
     166             :     // @Description: This sets the flap when in LOITER with soaring active. Overrides the usual auto flap behaviour.
     167             :     // @Range: 0 100
     168             :     // @User: Advanced
     169             :     AP_GROUPINFO("THML_FLAP", 22, SoaringController, soar_thermal_flap, 0),
     170             : 
     171             :     AP_GROUPEND
     172             : };
     173             : 
     174         692 : SoaringController::SoaringController(AP_TECS &tecs, const AP_FixedWing &parms) :
     175         692 :     _tecs(tecs),
     176         692 :     _vario(parms,_polarParams),
     177         692 :     _speedToFly(_polarParams),
     178         692 :     _aparm(parms),
     179        1384 :     _throttle_suppressed(true)
     180             : {
     181         692 :     AP_Param::setup_object_defaults(this, var_info);
     182         692 : }
     183             : 
     184       13618 : void SoaringController::get_target(Location &wp)
     185             : {
     186       13618 :     wp = AP::ahrs().get_home();
     187       13618 :     wp.offset(_position_x_filter.get(), _position_y_filter.get());
     188       13618 : }
     189             : 
     190       51738 : bool SoaringController::suppress_throttle()
     191             : {
     192       51738 :     float alt = _vario.alt;
     193             : 
     194       51738 :     if (_throttle_suppressed && (alt < alt_min)) {
     195             :         // Time to throttle up
     196           6 :         set_throttle_suppressed(false);
     197       51732 :     } else if ((!_throttle_suppressed) && (alt > alt_cutoff)) {
     198             :         // Start glide
     199           5 :         set_throttle_suppressed(true);
     200             : 
     201             :         // Zero the pitch integrator - the nose is currently raised to climb, we need to go back to glide.
     202           5 :         _tecs.reset_pitch_I();
     203             : 
     204           5 :         _cruise_start_time_us = AP_HAL::micros64();
     205             : 
     206             :         // Reset the filtered vario rate - it is currently elevated due to the climb rate and would otherwise take a while to fall again,
     207             :         // leading to false positives.
     208           5 :         _vario.reset_trigger_filter(0.0f);
     209             :     }
     210             : 
     211       51738 :     return _throttle_suppressed;
     212             : }
     213             : 
     214       34129 : bool SoaringController::check_thermal_criteria()
     215             : {
     216       34129 :     return (_last_update_status == ActiveStatus::AUTO_MODE_CHANGE
     217       24748 :             && ((AP_HAL::micros64() - _cruise_start_time_us) > ((unsigned)min_cruise_s * 1e6))
     218       22462 :             && (_vario.get_trigger_value() - _vario.get_exp_thermalling_sink()) > thermal_vspeed
     219           1 :             && _vario.alt < alt_max
     220       58877 :             && _vario.alt > alt_min);
     221             : }
     222             : 
     223             : 
     224       13618 : SoaringController::LoiterStatus SoaringController::check_cruise_criteria(Vector2f prev_wp, Vector2f next_wp)
     225             : {
     226             :     // Check conditions for re-entering cruise. Note that the aircraft needs to also be aligned with the appropriate
     227             :     // heading before some of these conditions will actually trigger.
     228             :     // The GCS messages are emitted in mode_thermal.cpp. Update these if the logic here is changed.
     229             : 
     230       13618 :     if (_last_update_status == ActiveStatus::SOARING_DISABLED) {
     231           0 :         return LoiterStatus::DISABLED;
     232             :     }
     233             : 
     234       13618 :     LoiterStatus result = LoiterStatus::GOOD_TO_KEEP_LOITERING;
     235       13618 :     const float alt = _vario.alt;
     236             : 
     237       13618 :     if (_exit_commanded) {
     238           0 :         result = LoiterStatus::EXIT_COMMANDED;
     239       13618 :     } else if (alt > alt_max) {
     240           1 :         result = LoiterStatus::ALT_TOO_HIGH;
     241       13617 :     } else if (alt < alt_min) {
     242           0 :         result = LoiterStatus::ALT_TOO_LOW;
     243       13617 :     } else if ((AP_HAL::micros64() - _thermal_start_time_us) > ((unsigned)min_thermal_s * 1e6)) {
     244       12367 :         const float mcCreadyAlt = McCready(alt);
     245       12367 :         if (_thermalability < mcCreadyAlt) {
     246           0 :             result = LoiterStatus::THERMAL_WEAK;
     247       12367 :         } else if (alt < (-_thermal_start_pos.z) || _vario.get_filtered_climb() < 0.0) {
     248           0 :             result = LoiterStatus::ALT_LOST;
     249       12367 :         } else if (check_drift(prev_wp, next_wp)) {
     250           0 :             result = LoiterStatus::DRIFT_EXCEEDED;
     251             :         }
     252             :     }
     253             : 
     254       13618 :     return result;
     255             : }
     256             : 
     257           1 : void SoaringController::init_thermalling()
     258             : {
     259             :     // Calc filter matrices - so that changes to parameters can be updated by switching in and out of thermal mode
     260           1 :     float r      = powf(thermal_r,  2); // Measurement noise
     261           1 :     float cov_q1 = powf(thermal_q1, 2); // Process noise for strength
     262           1 :     float cov_q2 = powf(thermal_q2, 2); // Process noise for position and radius
     263             : 
     264             :     const float init_q[4] = {cov_q1,
     265             :                              cov_q2,
     266             :                              cov_q2,
     267           1 :                              cov_q2};
     268             : 
     269           1 :     const MatrixN<float,4> q{init_q};
     270             : 
     271           1 :     const float init_p[4] = {INITIAL_STRENGTH_COVARIANCE,
     272             :                              INITIAL_RADIUS_COVARIANCE,
     273             :                              INITIAL_POSITION_COVARIANCE,
     274             :                              INITIAL_POSITION_COVARIANCE};
     275             : 
     276           1 :     const MatrixN<float,4> p{init_p};
     277             : 
     278           1 :     Vector3f position;
     279             : 
     280           1 :     const AP_AHRS &_ahrs = AP::ahrs();
     281           1 :     if (!_ahrs.get_relative_position_NED_home(position)) {
     282           0 :         return;
     283             :     }
     284             : 
     285             :     // New state vector filter will be reset. Thermal location is placed in front of a/c
     286           1 :     const float init_xr[4] = {_vario.get_trigger_value(),
     287             :                               INITIAL_THERMAL_RADIUS,
     288           1 :                               position.x + thermal_distance_ahead * cosf(_ahrs.get_yaw_rad()),
     289           2 :                               position.y + thermal_distance_ahead * sinf(_ahrs.get_yaw_rad())};
     290             : 
     291           1 :     const VectorN<float,4> xr{init_xr};
     292             : 
     293             :     // Also reset covariance matrix p so filter is not affected by previous data
     294           1 :     _ekf.reset(xr, p, q, r);
     295             : 
     296           1 :     _prev_update_time = AP_HAL::micros64();
     297           1 :     _thermal_start_time_us = AP_HAL::micros64();
     298           1 :     _thermal_start_pos = position;
     299             : 
     300           1 :     _vario.reset_climb_filter(0.0);
     301             : 
     302           1 :     _position_x_filter.reset(_ekf.X[2]);
     303           1 :     _position_y_filter.reset(_ekf.X[3]);
     304             : 
     305           1 :     _exit_commanded = false;
     306             : }
     307             : 
     308         266 : void SoaringController::init_cruising()
     309             : {
     310         266 :     if (_last_update_status >= ActiveStatus::MANUAL_MODE_CHANGE) {
     311           5 :         _cruise_start_time_us = AP_HAL::micros64();
     312             :         // Start glide. Will be updated on the next loop.
     313           5 :         set_throttle_suppressed(true);
     314             :     }
     315         266 : }
     316             : 
     317       13618 : void SoaringController::update_thermalling()
     318             : {
     319       13618 :     float deltaT = (AP_HAL::micros64() - _prev_update_time) * 1e-6;
     320             : 
     321       13618 :     Vector3f current_position;
     322             : 
     323       13618 :     const AP_AHRS &_ahrs = AP::ahrs();
     324       13618 :     if (!_ahrs.get_relative_position_NED_home(current_position)) {
     325           0 :         return;
     326             :     }
     327             : 
     328       13618 :     Vector3f wind;
     329             :     // use the estimate even if it is not marked valid, to preserve
     330             :     // existing behaviour
     331       13618 :     IGNORE_RETURN(_ahrs.get_wind(wind));
     332             : 
     333       13618 :     Vector3f wind_drift = wind*deltaT*_vario.get_filtered_climb()/_ekf.X[0];
     334             : 
     335             :     // update the filter
     336       13618 :     _ekf.update(_vario.reading, current_position.x, current_position.y, wind_drift.x, wind_drift.y);
     337             : 
     338             :     
     339       13618 :     _thermalability = (_ekf.X[0]*expf(-powf(get_thermalling_radius()/_ekf.X[1], 2))) - _vario.get_exp_thermalling_sink();
     340             : 
     341       13618 :     _prev_update_time = AP_HAL::micros64();
     342             : 
     343             :     // Compute smoothed estimate of position
     344       13618 :     _position_x_filter.set_cutoff_frequency(1/(3*_vario.tau));
     345       13618 :     _position_y_filter.set_cutoff_frequency(1/(3*_vario.tau));
     346             : 
     347       13618 :     _position_x_filter.apply(_ekf.X[2], deltaT);
     348       13618 :     _position_y_filter.apply(_ekf.X[3], deltaT);
     349             : 
     350             : #if HAL_LOGGING_ENABLED
     351             :     // write log - save the data.
     352             :     // @LoggerMessage: SOAR
     353             :     // @Vehicles: Plane
     354             :     // @Description: Logged data from soaring feature
     355             :     // @URL: https://ardupilot.club/plane/docs/soaring.html
     356             :     // @Field: TimeUS: microseconds since system startup
     357             :     // @Field: nettorate: Estimate of vertical speed of surrounding airmass
     358             :     // @Field: x0: Thermal strength estimate
     359             :     // @Field: x1: Thermal radius estimate
     360             :     // @Field: x2: Thermal position estimate north from home
     361             :     // @Field: x3: Thermal position estimate east from home
     362             :     // @Field: north: Aircraft position north from home
     363             :     // @Field: east: Aircraft position east from home
     364             :     // @Field: alt: Aircraft altitude
     365             :     // @Field: dx_w: Wind speed north
     366             :     // @Field: dy_w: Wind speed east
     367             :     // @Field: th: Estimate of achievable climbrate in thermal
     368       27236 :     AP::logger().WriteStreaming("SOAR", "TimeUS,nettorate,x0,x1,x2,x3,north,east,alt,dx_w,dy_w,th", "Qfffffffffff",
     369             :                                            AP_HAL::micros64(),
     370       13618 :                                            (double)_vario.reading,
     371       13618 :                                            (double)_ekf.X[0],
     372       13618 :                                            (double)_ekf.X[1],
     373       13618 :                                            (double)_ekf.X[2],
     374       13618 :                                            (double)_ekf.X[3],
     375       13618 :                                            current_position.x,
     376       13618 :                                            current_position.y,
     377       13618 :                                            (double)_vario.alt,
     378       13618 :                                            (double)wind_drift.x,
     379       13618 :                                            (double)wind_drift.y,
     380       13618 :                                            (double)_thermalability);
     381             : #if HAL_SOARING_NVF_EKF_ENABLED
     382             :     auto const now_ms = AP_HAL::millis();
     383             :     if (now_ms - _prev_nvf_pub_time_ms > NVF_PUBLISHER_DELAY_MS) {
     384             :         gcs().send_named_float("SOAREKFX0", (float)_ekf.X[0]);
     385             :         gcs().send_named_float("SOAREKFX1", (float)_ekf.X[1]);
     386             :         gcs().send_named_float("SOAREKFX2", (float)_ekf.X[2]);
     387             :         gcs().send_named_float("SOAREKFX3", (float)_ekf.X[3]);
     388             :         _prev_nvf_pub_time_ms = now_ms;
     389             :     }
     390             : 
     391             : #endif // HAL_SOARING_NVF_EKF_ENABLED
     392             : #endif
     393             : }
     394             : 
     395       34129 : void SoaringController::update_cruising()
     396             : {
     397             :     // Calculate the optimal airspeed for the current conditions of wind along current direction,
     398             :     // expected lift in next thermal and filtered sink rate.
     399             : 
     400       34129 :     Vector3f wind;
     401             :     // use the estimate even if it is not marked valid, to preserve
     402             :     // existing behaviour
     403       34129 :     IGNORE_RETURN(AP::ahrs().get_wind(wind));
     404       34129 :     Vector3f wind_bf = AP::ahrs().earth_to_body(wind);
     405             : 
     406       34129 :     const float wx = wind_bf.x;
     407             : 
     408       34129 :     const float wz = _vario.get_stf_value();
     409             : 
     410             :     // Constraints on the airspeed calculation.
     411       34129 :     const float CLmin = _polarParams.K/(_aparm.airspeed_max*_aparm.airspeed_max);
     412       34129 :     const float CLmax = _polarParams.K/(_aparm.airspeed_min*_aparm.airspeed_min);
     413             : 
     414             :     // Update the calculation.
     415       34129 :     _speedToFly.update(wx, wz, thermal_vspeed, CLmin, CLmax);
     416             : 
     417             : #if HAL_LOGGING_ENABLED
     418             :     // @LoggerMessage: SORC
     419             :     // @Vehicles: Plane
     420             :     // @Description: Soaring Cruise-phase data
     421             :     // @URL: https://ardupilot.club/plane/docs/soaring.html
     422             :     // @Field: TimeUS: Time since system startup
     423             :     // @Field: wx: body-frame wind estimate, x-axis
     424             :     // @Field: wz: body-frame wind estimate, z-axis
     425             :     // @Field: wexp: estimated thermal vertical speed
     426             :     // @Field: CLmin: expected climb-rate lower-limit
     427             :     // @Field: CLmax: expected climb-rate upper-limit
     428             :     // @Field: Vopt: calculated optimal speed to fly
     429       68258 :     AP::logger().WriteStreaming("SORC", "TimeUS,wx,wz,wexp,CLmin,CLmax,Vopt", "Qffffff",
     430             :                                        AP_HAL::micros64(),
     431             :                                        (double)wx,
     432             :                                        (double)wz,
     433             :                                        (double)thermal_vspeed,
     434             :                                        (double)CLmin,
     435             :                                        (double)CLmax,
     436       34129 :                                        (double)_speedToFly.speed_to_fly());
     437             : #endif
     438       34129 : }
     439             : 
     440       68767 : void SoaringController::update_vario()
     441             : {
     442       68767 :     _vario.update(thermal_bank);
     443       68767 : }
     444             : 
     445             : 
     446       12367 : float SoaringController::McCready(float alt)
     447             : {
     448             :     // A method shell to be filled in later
     449       12367 :     return thermal_vspeed;
     450             : }
     451             : 
     452     2736341 : SoaringController::ActiveStatus SoaringController::active_state(bool override_disable) const
     453             : {
     454     2736341 :     if (override_disable || !soar_active) {
     455     2658379 :         return ActiveStatus::SOARING_DISABLED;
     456             :     }
     457             : 
     458       77962 :     return _pilot_desired_state;
     459             : }
     460             : 
     461     2736341 : void SoaringController::update_active_state(bool override_disable)
     462             : {
     463     2736341 :     ActiveStatus status = active_state(override_disable);
     464     2736341 :     bool state_changed = !(status == _last_update_status);
     465             : 
     466     2736341 :     if (state_changed) {
     467          14 :         switch (status) {
     468           4 :             case ActiveStatus::SOARING_DISABLED:
     469             :                 // It's not enabled, but was enabled on the last loop.
     470           4 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Soaring: Disabled.");
     471           4 :                 set_throttle_suppressed(false);
     472           4 :                 break;
     473           8 :             case ActiveStatus::MANUAL_MODE_CHANGE:
     474             :                 // It's enabled, but wasn't on the last loop.
     475           8 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Soaring: Enabled, manual mode changes.");
     476           8 :                 break;
     477           2 :             case ActiveStatus::AUTO_MODE_CHANGE:
     478           2 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Soaring: Enabled, automatic mode changes.");
     479           2 :                 break;
     480             :         }
     481             : 
     482          14 :         if (_last_update_status == ActiveStatus::SOARING_DISABLED) {
     483             :             // We have switched from disabled into an active mode, start cruising.
     484           9 :             init_cruising();
     485           5 :         } else if (status != ActiveStatus::SOARING_DISABLED) {
     486             :             // We switched between active modes. If we're in THERMAL this means we should exit gracefully.
     487             :             // This has no effect if we're cruising as it is reset on thermal entry.
     488           1 :             _exit_commanded = true;
     489             :         }
     490             :     }
     491             : 
     492     2736341 :     _last_update_status = status;
     493     2736341 : }
     494             : 
     495             : 
     496       17049 : void SoaringController::set_throttle_suppressed(bool suppressed)
     497             : {
     498       17049 :     _throttle_suppressed = suppressed;
     499             : 
     500             :     // Let the TECS know.
     501       17049 :     _tecs.set_gliding_requested_flag(suppressed);
     502       17049 : }
     503             : 
     504       12367 : bool SoaringController::check_drift(Vector2f prev_wp, Vector2f next_wp)
     505             : {
     506             :     // Check for -1 (disabled)
     507       12367 :     if (max_drift<0) {
     508       12367 :         return false;
     509             :     }
     510             : 
     511             :     // Check against the estimated thermal.
     512           0 :     Vector2f position(_ekf.X[2], _ekf.X[3]);
     513             : 
     514           0 :     Vector2f start_pos(_thermal_start_pos.x, _thermal_start_pos.y);
     515             : 
     516           0 :     Vector2f mission_leg = next_wp - prev_wp;
     517             : 
     518           0 :     if (prev_wp.is_zero() || mission_leg.length() < 0.1) {
     519             :         // Simple check of distance from initial start point.
     520           0 :         return (position - start_pos).length() > max_drift;
     521             :     } else {
     522             :         // Regard the effective start point as projected onto mission leg.
     523             :         // Calculate drift parallel and perpendicular to mission leg.
     524             :         // Drift parallel and in direction of mission leg is acceptable.
     525           0 :         Vector2f effective_start, vec1, vec2;
     526             : 
     527             :         // Calculate effective start point (on mission leg).
     528           0 :         vec1 = (start_pos - prev_wp).projected(mission_leg);
     529           0 :         effective_start = prev_wp + vec1;
     530             : 
     531             :         // Calculate parallel and perpendicular offsets.
     532           0 :         vec2 = position - effective_start;
     533             : 
     534           0 :         float parallel      = vec2 * mission_leg.normalized();
     535           0 :         float perpendicular = (vec2 - mission_leg.normalized()*parallel).length();
     536             : 
     537             :        // Check if we've drifted beyond the next wp.
     538           0 :         if (parallel>(next_wp - effective_start).length()) {
     539           0 :             return true;
     540             :         }
     541             : 
     542             :         // Check if we've drifted too far laterally or backwards. We don't count positive parallel offsets
     543             :         // as these are favourable (towards next wp)
     544           0 :         parallel = parallel>0 ? 0 : parallel;
     545             : 
     546           0 :         return (powf(parallel,2)+powf(perpendicular,2)) > powf(max_drift,2);
     547             :     }
     548             : }
     549             : 
     550       16342 : float SoaringController::get_thermalling_radius() const
     551             : {
     552             :     // Thermalling radius is controlled by parameter SOAR_THML_BANK and true target airspeed.
     553       16342 :     const float target_aspd = _tecs.get_target_airspeed() * AP::ahrs().get_EAS2TAS();
     554       16342 :     const float radius = (target_aspd*target_aspd) / (GRAVITY_MSS * tanf(thermal_bank*DEG_TO_RAD));
     555             : 
     556       16342 :     return radius;
     557             : }
     558             : 
     559        2724 : float SoaringController::get_thermalling_target_airspeed()
     560             : {
     561        2724 :     return soar_thermal_airspeed;
     562             : }
     563             : 
     564        5458 : float SoaringController::get_cruising_target_airspeed()
     565             : {
     566        5458 :     if (soar_cruise_airspeed<0) {
     567         623 :         return _speedToFly.speed_to_fly();
     568             :     }
     569        4835 :     return soar_cruise_airspeed;
     570             : }
     571             : 
     572             : #endif // HAL_SOARING_ENABLED

Generated by: LCOV version 1.14