LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/Rover - sailboat.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 150 192 78.1 %
Date: 2026-09-09 11:02:08 Functions: 16 19 84.2 %

          Line data    Source code
       1             : #include "Rover.h"
       2             : 
       3             : #define SAILBOAT_AUTO_TACKING_TIMEOUT_MS 5000   // tacks in auto mode timeout if not successfully completed within this many milliseconds
       4             : #define SAILBOAT_TACKING_ACCURACY_DEG 10        // tack is considered complete when vehicle is within this many degrees of target tack angle
       5             : #define SAILBOAT_NOGO_PAD 10                    // deg, the no go zone is padded by this much when deciding if we should use the Sailboat heading controller
       6             : #define TACK_RETRY_TIME_MS 5000                 // Can only try another auto mode tack this many milliseconds after the last is cleared (either competed or timed-out)
       7             : /*
       8             : To Do List
       9             :  - Improve tacking in light winds and bearing away in strong wings
      10             :  - consider drag vs lift sailing differences, ie upwind sail is like wing, dead down wind sail is like parachute
      11             :  - max speed parameter and controller, for mapping you may not want to go too fast
      12             :  - mavlink sailing messages
      13             :  - smart decision making, ie tack on windshifts, what to do if stuck head to wind
      14             :  - some sailing codes track waves to try and 'surf' and to allow tacking on a flat bit, not sure if there is much gain to be had here
      15             :  - add some sort of pitch monitoring to prevent nose diving in heavy weather
      16             :  - pitch PID for hydrofoils
      17             :  - more advanced sail control, ie twist
      18             :  - independent sheeting for main and jib
      19             :  - tack on depth sounder info to stop sailing into shallow water on indirect sailing routes
      20             :  - add option to do proper tacks, ie tacking on flat spot in the waves, or only try once at a certain speed, or some better method than just changing the desired heading suddenly
      21             : */
      22             : 
      23             : const AP_Param::GroupInfo Sailboat::var_info[] = {
      24             : 
      25             :     // @Param: ENABLE
      26             :     // @DisplayName: Enable Sailboat
      27             :     // @Description: This enables Sailboat functionality
      28             :     // @Values: 0:Disable,1:Enable
      29             :     // @User: Standard
      30             :     // @RebootRequired: True
      31             :     AP_GROUPINFO_FLAGS("ENABLE", 1, Sailboat, enable, 0, AP_PARAM_FLAG_ENABLE),
      32             : 
      33             :     // @Param: ANGLE_MIN
      34             :     // @DisplayName: Sail min angle
      35             :     // @Description: Mainsheet tight, angle between centerline and boom
      36             :     // @Units: deg
      37             :     // @Range: 0 90
      38             :     // @Increment: 1
      39             :     // @User: Standard
      40             :     AP_GROUPINFO("ANGLE_MIN", 2, Sailboat, sail_angle_min, 0),
      41             : 
      42             :     // @Param: ANGLE_MAX
      43             :     // @DisplayName: Sail max angle
      44             :     // @Description: Mainsheet loose, angle between centerline and boom. For direct-control rotating masts, the rotation angle at SERVOx_MAX/_MIN; for rotating masts, this value can exceed 90 degrees if the linkages can physically rotate the mast past that angle.
      45             :     // @Units: deg
      46             :     // @Range: 0 90
      47             :     // @Increment: 1
      48             :     // @User: Standard
      49             :     AP_GROUPINFO("ANGLE_MAX", 3, Sailboat, sail_angle_max, 90),
      50             : 
      51             :     // @Param: ANGLE_IDEAL
      52             :     // @DisplayName: Sail ideal angle
      53             :     // @Description: Ideal angle between sail and apparent wind
      54             :     // @Units: deg
      55             :     // @Range: 0 90
      56             :     // @Increment: 1
      57             :     // @User: Standard
      58             :     AP_GROUPINFO("ANGLE_IDEAL", 4, Sailboat, sail_angle_ideal, 25),
      59             : 
      60             :     // @Param: HEEL_MAX
      61             :     // @DisplayName: Sailing maximum heel angle
      62             :     // @Description: When in auto sail trim modes the heel will be limited to this value using PID control
      63             :     // @Units: deg
      64             :     // @Range: 0 90
      65             :     // @Increment: 1
      66             :     // @User: Standard
      67             :     AP_GROUPINFO("HEEL_MAX", 5, Sailboat, sail_heel_angle_max, 15),
      68             : 
      69             :     // @Param: NO_GO_ANGLE
      70             :     // @DisplayName: Sailing no go zone angle
      71             :     // @Description: The typical closest angle to the wind the vehicle will sail at. the vehicle will sail at this angle when going upwind
      72             :     // @Units: deg
      73             :     // @Range: 0 90
      74             :     // @Increment: 1
      75             :     // @User: Standard
      76             :     AP_GROUPINFO("NO_GO_ANGLE", 6, Sailboat, sail_no_go, 45),
      77             : 
      78             :     // @Param: WNDSPD_MIN
      79             :     // @DisplayName: Sailboat minimum wind speed to sail in
      80             :     // @Description: Sailboat minimum wind speed to continue sail in, at lower wind speeds the sailboat will motor if one is fitted
      81             :     // @Units: m/s
      82             :     // @Range: 0 5
      83             :     // @Increment: 0.1
      84             :     // @User: Standard
      85             :     AP_GROUPINFO("WNDSPD_MIN", 7, Sailboat, sail_windspeed_min, 0),
      86             : 
      87             :     // @Param: XTRACK_MAX
      88             :     // @DisplayName: Sailing vehicle max cross track error
      89             :     // @Description: The sail boat will tack when it reaches this cross track error, defines a corridor of 2 times this value wide, 0 disables
      90             :     // @Units: m
      91             :     // @Range: 5 25
      92             :     // @Increment: 1
      93             :     // @User: Standard
      94             :     AP_GROUPINFO("XTRACK_MAX", 8, Sailboat, xtrack_max, 10),
      95             : 
      96             :     // @Param: LOIT_RADIUS
      97             :     // @DisplayName: Loiter radius
      98             :     // @Description: When in sailing modes the vehicle will keep moving within this loiter radius
      99             :     // @Units: m
     100             :     // @Range: 0 20
     101             :     // @Increment: 1
     102             :     // @User: Standard
     103             :     AP_GROUPINFO("LOIT_RADIUS", 9, Sailboat, loit_radius, 5),
     104             : 
     105             :     AP_GROUPEND
     106             : };
     107             : 
     108             : 
     109             : /*
     110             :   constructor
     111             :  */
     112         285 : Sailboat::Sailboat()
     113             : {
     114         285 :     AP_Param::setup_object_defaults(this, var_info);
     115         285 : }
     116             : 
     117             : // true if sailboat navigation (aka tacking) is enabled
     118      457514 : bool Sailboat::tack_enabled() const
     119             : {
     120             :     // tacking disabled if not a sailboat
     121      457514 :     if (!sail_enabled()) {
     122      414537 :         return false;
     123             :     }
     124             : 
     125             :     // tacking disabled if motor is always on
     126       42977 :     if (motor_state == UseMotor::USE_MOTOR_ALWAYS) {
     127           0 :         return false;
     128             :     }
     129             : 
     130             :     // disable tacking if motor is available and wind is below cutoff
     131       42977 :     if (motor_assist_low_wind()) {
     132           0 :         return false;
     133             :     }
     134             : 
     135             :     // otherwise tacking is enabled
     136       42977 :     return true;
     137             : }
     138             : 
     139         283 : void Sailboat::init()
     140             : {
     141             :     // sailboat defaults
     142         283 :     if (sail_enabled()) {
     143           4 :         rover.g2.crash_angle.set_default(0);
     144             : 
     145             :         // sailboats without motors may travel faster than WP_SPEED so allow waypoint navigation to
     146             :         // speedup to catch the vehicle instead of asking the vehicle to slow down
     147           4 :         rover.g2.wp_nav.enable_overspeed(motor_state != UseMotor::USE_MOTOR_ALWAYS);
     148             :     }
     149             : 
     150         283 :     if (tack_enabled()) {
     151           4 :         rover.g2.loit_type.set_default(1);
     152             :     }
     153             : 
     154             :     // initialise motor state to USE_MOTOR_ASSIST
     155             :     // this will silently fail if there is no motor attached
     156         283 :     set_motor_state(UseMotor::USE_MOTOR_ASSIST, false);
     157         283 : }
     158             : 
     159             : // initialise rc input (channel_mainsail), may be called intermittently
     160       12811 : void Sailboat::init_rc_in()
     161             : {
     162             :     // get auxiliary throttle value
     163       12811 :     RC_Channel *rc_ptr = rc().find_channel_for_option(RC_Channel::AUX_FUNC::MAINSAIL);
     164       12811 :     if (rc_ptr != nullptr) {
     165             :         // use aux as sail input if defined
     166           0 :         channel_mainsail = rc_ptr;
     167           0 :         channel_mainsail->set_angle(100);
     168           0 :         channel_mainsail->set_default_dead_zone(30);
     169             :     } else {
     170             :         // use throttle channel
     171       12811 :         channel_mainsail = rover.channel_throttle;
     172             :     }
     173       12811 : }
     174             : 
     175             : /// @brief decode pilot mainsail input in manual modes and update the various
     176             : /// sail actuator values for different sail types ready for SRV_Channel output.
     177      362133 : void Sailboat::set_pilot_desired_mainsail()
     178             : {
     179             :     // no RC input means mainsail is moved to trim
     180      362133 :     if ((rover.failsafe.bits & FAILSAFE_EVENT_THROTTLE) || (channel_mainsail == nullptr)) {
     181         217 :        relax_sails();
     182             :     } else {
     183      361916 :        rover.g2.motors.set_mainsail(constrain_float(channel_mainsail->get_control_in(), 0.0f, 100.0f));
     184      361916 :        rover.g2.motors.set_wingsail(constrain_float(channel_mainsail->get_control_in(), -100.0f, 100.0f));
     185      361916 :        rover.g2.motors.set_mast_rotation(constrain_float(channel_mainsail->get_control_in(), -100.0f, 100.0f));
     186             :     }
     187      362133 : }
     188             : 
     189             : /// @brief Set mainsail in auto modes
     190             : /// @param[in] desired_speed desired speed (in m/s) only used to detect desired direction
     191       17468 : void Sailboat::set_auto_mainsail(float desired_speed)
     192             : {
     193             :     // use PID controller to sheet out, this number is expected approximately in the 0 to 100 range (with default PIDs)
     194       17468 :     const float pid_offset = rover.g2.attitude_control.get_sail_out_from_heel(radians(sail_heel_angle_max), rover.G_Dt) * 100.0f;
     195             : 
     196             :     // get apparent wind, + is wind over starboard side, - is wind over port side
     197       17468 :     const float wind_dir_apparent = degrees(rover.g2.windvane.get_apparent_wind_direction_rad());
     198       17468 :     const float wind_dir_apparent_abs = fabsf(wind_dir_apparent);
     199       17468 :     const float wind_dir_apparent_sign = is_negative(wind_dir_apparent) ? -1.0f : 1.0f;
     200             : 
     201             :     //
     202             :     // mainsail control.
     203             :     //
     204             :     // mainsail_out represents a range from 0 to 100
     205       17468 :     float mainsail_out = 100.0f;
     206             :     // main sails cannot be used to reverse
     207       17468 :     if (is_positive(desired_speed)) {
     208             :         // Sails are sheeted the same on each side use abs wind direction
     209             :         // set the main sail to the ideal angle to the wind
     210             :         const float mainsail_angle =
     211       17460 :             constrain_float(wind_dir_apparent_abs - sail_angle_ideal,sail_angle_min, sail_angle_max);
     212             : 
     213             :         // linear interpolate mainsail value (0 to 100) from wind angle mainsail_angle
     214       17460 :         const float mainsail_base = linear_interpolate(0.0f, 100.0f, mainsail_angle,sail_angle_min,sail_angle_max);
     215             : 
     216       17460 :         mainsail_out = constrain_float((mainsail_base + pid_offset), 0.0f ,100.0f);
     217             :     }
     218       17468 :     rover.g2.motors.set_mainsail(mainsail_out);
     219             :     //
     220             :     // wingsail control
     221             :     // wing sails auto trim, we only need to reduce power if we are tipping over, must also be trimmed for correct tack
     222             :     // dont allow to reduce power to less than 0, ie not backwinding the sail to self-right
     223             :     // wing sails can be used to go backwards, probably not recommended though
     224       17468 :     const float wing_sail_out_sign = is_negative(desired_speed) ? -1.0f : 1.0f;
     225       17468 :     const float wingsail_out = (100.0f - MIN(pid_offset,100.0f)) * wind_dir_apparent_sign * wing_sail_out_sign;
     226       17468 :     rover.g2.motors.set_wingsail(wingsail_out);
     227             :     //
     228             :     // direct mast rotation control
     229             :     //
     230       17468 :     float mast_rotation_out = 0.0f;
     231       17468 :     if (is_positive(desired_speed)) {
     232             :         // rotating sails can be used to reverse, but not in this version
     233       17460 :         if (wind_dir_apparent_abs < sail_angle_ideal) {
     234             :             // in irons, center the sail.
     235        1238 :             mast_rotation_out = 0.0f;
     236             :         } else {
     237             :             float mast_rotation_angle;
     238       16222 :             if (wind_dir_apparent_abs < (90.0f + sail_angle_ideal)) {
     239             :                 // use sail as a lift device, at ideal angle of attack, but depower to prevent excessive heel
     240             :                 // multiply pid_offset by 0.01 to keep the scaling in the same range as the other sail outputs
     241             :                 // this means the default PIDs should apply reasonably well to all sail types
     242       15194 :                 mast_rotation_angle = wind_dir_apparent_abs - sail_angle_ideal * MAX(1.0f - pid_offset*0.01f,0.0f);
     243             : 
     244             :                 // restore sign
     245       15194 :                 mast_rotation_angle *= wind_dir_apparent_sign;
     246             : 
     247             :             } else {
     248             :                 // use sail as drag device, but avoid wagging the sail as the wind oscillates
     249             :                 // between 180 and -180 degrees
     250        1028 :                 mast_rotation_angle = 90.0f;
     251        1028 :                 if (wind_dir_apparent_abs > 135.0f) {
     252             :                     // wind is almost directly behind, keep wing on current tack
     253         764 :                     if (is_negative(SRV_Channels::get_output_scaled(SRV_Channel::k_mast_rotation))) {
     254         152 :                         mast_rotation_angle *= -1.0f;
     255             :                     }
     256             :                 } else {
     257             :                     // set the wing on the correct tack, so that is can be sheeted in if required
     258         264 :                     mast_rotation_angle *= wind_dir_apparent_sign;
     259             :                 }
     260             :             }
     261             :             // linear interpolate servo displacement (-100 to 100) from mast rotation angle and restore sign
     262       16222 :             mast_rotation_out = linear_interpolate(-100.0f, 100.0f, mast_rotation_angle, -sail_angle_max, sail_angle_max);
     263             :         }
     264             :     }
     265       17468 :     rover.g2.motors.set_mast_rotation(mast_rotation_out);
     266       17468 : }
     267             : 
     268       47521 : void Sailboat::relax_sails()
     269             : {
     270       47521 :     rover.g2.motors.set_mainsail(100.0f);
     271       47521 :     rover.g2.motors.set_wingsail(0.0f);
     272       47521 :     rover.g2.motors.set_mast_rotation(0.0f);
     273       47521 : }
     274             : 
     275             : // calculate throttle and mainsail angle required to attain desired speed (in m/s)
     276       17468 : void Sailboat::get_throttle_and_set_mainsail(float desired_speed, float &throttle_out)
     277             : {
     278       17468 :     throttle_out = 0.0f;
     279       17468 :     if (!sail_enabled()) {
     280           0 :         relax_sails();
     281           0 :         return;
     282             :     }
     283             : 
     284             :     // run speed controller if motor is forced on or motor assistance is required for low speeds or tacking
     285       17468 :     if ((motor_state == UseMotor::USE_MOTOR_ALWAYS) ||
     286       34936 :          motor_assist_tack() ||
     287       17468 :          motor_assist_low_wind()) {
     288             :         // run speed controller - duplicate of calls found in mode::calc_throttle();
     289           0 :         throttle_out = 100.0f * rover.g2.attitude_control.get_throttle_out_speed(desired_speed,
     290           0 :                                                                         rover.g2.motors.limit.throttle_lower,
     291           0 :                                                                         rover.g2.motors.limit.throttle_upper,
     292             :                                                                         rover.g.speed_cruise,
     293           0 :                                                                         rover.g.throttle_cruise * 0.01f,
     294             :                                                                         rover.G_Dt);
     295             :     }
     296             : 
     297       17468 :     if (motor_state == UseMotor::USE_MOTOR_ALWAYS) {
     298           0 :         relax_sails();
     299             :     } else {
     300       17468 :         set_auto_mainsail(desired_speed);
     301             :     }
     302             : }
     303             : 
     304             : // Velocity Made Good, this is the speed we are traveling towards the desired destination
     305             : // only for logging at this stage
     306             : // https://en.wikipedia.org/wiki/Velocity_made_good
     307       20419 : float Sailboat::get_VMG() const
     308             : {
     309             :     // return zero if we don't have a valid speed
     310             :     float speed;
     311       20419 :     if (!rover.g2.attitude_control.get_forward_speed(speed)) {
     312         237 :         return 0.0f;
     313             :     }
     314             : 
     315             :     // return speed if not heading towards a waypoint
     316       20182 :     if (!rover.control_mode->is_autopilot_mode()) {
     317        2671 :         return speed;
     318             :     }
     319             : 
     320       17511 :     return (speed * cosf(wrap_PI(radians(rover.g2.wp_nav.wp_bearing_cd() * 0.01f) - rover.ahrs.get_yaw_rad())));
     321             : }
     322             : 
     323             : // handle user initiated tack while in acro mode
     324           0 : void Sailboat::handle_tack_request_acro()
     325             : {
     326           0 :     if (!tack_enabled() || currently_tacking) {
     327           0 :         return;
     328             :     }
     329             :     // set tacking heading target to the current angle relative to the true wind but on the new tack
     330           0 :     currently_tacking = true;
     331           0 :     tack_heading_rad = wrap_2PI(rover.ahrs.get_yaw_rad() + 2.0f * wrap_PI((rover.g2.windvane.get_true_wind_direction_rad() - rover.ahrs.get_yaw_rad())));
     332             : 
     333           0 :     tack_request_ms = AP_HAL::millis();
     334             : }
     335             : 
     336             : // return target heading in radians when tacking (only used in acro)
     337           0 : float Sailboat::get_tack_heading_rad()
     338             : {
     339           0 :     if (fabsf(wrap_PI(tack_heading_rad - rover.ahrs.get_yaw_rad())) < radians(SAILBOAT_TACKING_ACCURACY_DEG) ||
     340           0 :        ((AP_HAL::millis() - tack_request_ms) > SAILBOAT_AUTO_TACKING_TIMEOUT_MS)) {
     341           0 :         clear_tack();
     342             :     }
     343             : 
     344           0 :     return tack_heading_rad;
     345             : }
     346             : 
     347             : // handle user initiated tack while in autonomous modes (Auto, Guided, RTL, SmartRTL, etc)
     348           0 : void Sailboat::handle_tack_request_auto()
     349             : {
     350           0 :     if (!tack_enabled() || currently_tacking) {
     351           0 :         return;
     352             :     }
     353             : 
     354             :     // record time of request for tack.  This will be processed asynchronously by sailboat_calc_heading
     355           0 :     tack_request_ms = AP_HAL::millis();
     356             : }
     357             : 
     358             : // clear tacking state variables
     359         567 : void Sailboat::clear_tack()
     360             : {
     361         567 :     currently_tacking = false;
     362         567 :     tack_assist = false;
     363         567 :     tack_request_ms = 0;
     364         567 :     tack_clear_ms = AP_HAL::millis();
     365         567 : }
     366             : 
     367             : // returns true if boat is currently tacking
     368        7017 : bool Sailboat::tacking() const
     369             : {
     370        7017 :     return tack_enabled() && currently_tacking;
     371             : }
     372             : 
     373             : // returns true if sailboat should take a indirect navigation route to go upwind
     374             : // desired_heading should be in centi-degrees
     375      192980 : bool Sailboat::use_indirect_route(float desired_heading_cd) const
     376             : {
     377      192980 :     if (!tack_enabled()) {
     378      175512 :         return false;
     379             :     }
     380             : 
     381             :     // use sailboat controller until tack is completed
     382       17468 :     if (currently_tacking) {
     383         783 :         return true;
     384             :     }
     385             : 
     386             :     // convert desired heading to radians
     387       16685 :     const float desired_heading_rad = radians(desired_heading_cd * 0.01f);
     388             : 
     389             :     // check if desired heading is in the no go zone, if it is we can't go direct
     390             :     // pad no go zone, this allows use of heading controller rather than L1 when close to the wind
     391       16685 :     return fabsf(wrap_PI(rover.g2.windvane.get_true_wind_direction_rad() - desired_heading_rad)) <= radians(sail_no_go + SAILBOAT_NOGO_PAD);
     392             : }
     393             : 
     394             : // if we can't sail on the desired heading then we should pick the best heading that we can sail on
     395             : // this function assumes the caller has already checked sailboat_use_indirect_route(desired_heading_cd) returned true
     396        3028 : float Sailboat::calc_heading(float desired_heading_cd)
     397             : {
     398        3028 :     if (!tack_enabled()) {
     399           0 :         return desired_heading_cd;
     400             :     }
     401        3028 :     bool should_tack = false;
     402             : 
     403             :     // find which tack we are on
     404        3028 :     const AP_WindVane::Sailboat_Tack current_tack = rover.g2.windvane.get_current_tack();
     405             : 
     406             :     // convert desired heading to radians
     407        3028 :     const float desired_heading_rad = radians(desired_heading_cd * 0.01f);
     408             : 
     409             :     // if the desired heading is outside the no go zone there is no need to change it
     410             :     // this allows use of heading controller rather than L1 when desired
     411             :     // this is used in the 'SAILBOAT_NOGO_PAD' region
     412        3028 :     const float true_wind_rad = rover.g2.windvane.get_true_wind_direction_rad();
     413        3028 :     if (fabsf(wrap_PI(true_wind_rad - desired_heading_rad)) > radians(sail_no_go) && !currently_tacking) {
     414             : 
     415             :         // calculate the tack the new heading would be on
     416         495 :         const float new_heading_apparent_angle = wrap_PI(true_wind_rad - desired_heading_rad);
     417             :         AP_WindVane::Sailboat_Tack new_tack;
     418         495 :         if (is_negative(new_heading_apparent_angle)) {
     419         131 :             new_tack = AP_WindVane::Sailboat_Tack::TACK_PORT;
     420             :         } else {
     421         364 :             new_tack = AP_WindVane::Sailboat_Tack::TACK_STARBOARD;
     422             :         }
     423             : 
     424             :         // if the new tack is not the same as the current tack we need might need to tack
     425         495 :         if (new_tack != current_tack) {
     426             :             // see if it would be a tack, the front of the boat going through the wind
     427             :             // or a gybe, the back of the boat going through the wind
     428         241 :             const float app_wind_rad = rover.g2.windvane.get_apparent_wind_direction_rad();
     429         241 :             if (fabsf(app_wind_rad) + fabsf(new_heading_apparent_angle) < M_PI) {
     430         201 :                 should_tack = true;
     431             :             }
     432             :         }
     433             : 
     434         495 :         if (!should_tack) {
     435         294 :             return desired_heading_cd;
     436             :         }
     437             :     }
     438             : 
     439             :     // check for user requested tack
     440        2734 :     uint32_t now = AP_HAL::millis();
     441        2734 :     if (tack_request_ms != 0 && !should_tack && !currently_tacking) {
     442             :         // set should_tack flag is user requested tack within last 0.5 sec
     443           0 :         should_tack = ((now - tack_request_ms) < 500);
     444           0 :         tack_request_ms = 0;
     445             :     }
     446             : 
     447             :     // trigger tack if cross track error larger than xtrack_max parameter
     448             :     // this effectively defines a 'corridor' of width 2*xtrack_max that the boat will stay within
     449        2734 :     const float cross_track_error = rover.g2.wp_nav.crosstrack_error_m();
     450        2734 :     if ((fabsf(cross_track_error) >= xtrack_max) && !is_zero(xtrack_max) && !should_tack && !currently_tacking) {
     451             :         // make sure the new tack will reduce the cross track error
     452             :         // if were on starboard tack we are traveling towards the left hand boundary
     453           0 :         if (is_positive(cross_track_error) && (current_tack == AP_WindVane::Sailboat_Tack::TACK_STARBOARD)) {
     454           0 :             should_tack = true;
     455             :         }
     456             :         // if were on port tack we are traveling towards the right hand boundary
     457           0 :         if (is_negative(cross_track_error) && (current_tack == AP_WindVane::Sailboat_Tack::TACK_PORT)) {
     458           0 :             should_tack = true;
     459             :         }
     460             :     }
     461             : 
     462             :     // calculate left and right no go headings looking upwind, Port tack heading is left no-go, STBD tack is right of no-go
     463        2734 :     const float left_no_go_heading_rad = wrap_2PI(true_wind_rad + radians(sail_no_go));
     464        2734 :     const float right_no_go_heading_rad = wrap_2PI(true_wind_rad - radians(sail_no_go));
     465             : 
     466             :     // if tack triggered, calculate target heading
     467        2734 :     if (should_tack && (now - tack_clear_ms) > TACK_RETRY_TIME_MS) {
     468          11 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Sailboat: Tacking");
     469             :         // calculate target heading for the new tack
     470          11 :         switch (current_tack) {
     471           4 :             case AP_WindVane::Sailboat_Tack::TACK_PORT:
     472           4 :                 tack_heading_rad = right_no_go_heading_rad;
     473           4 :                 break;
     474           7 :             case AP_WindVane::Sailboat_Tack::TACK_STARBOARD:
     475           7 :                 tack_heading_rad = left_no_go_heading_rad;
     476           7 :                 break;
     477             :         }
     478          11 :         currently_tacking = true;
     479          11 :         auto_tack_start_ms = now;
     480             :     }
     481             : 
     482             :     // if we are tacking we maintain the target heading until the tack completes or times out
     483        2734 :     if (currently_tacking) {
     484             :         // check if we have reached target
     485         794 :         if (fabsf(wrap_PI(tack_heading_rad - rover.ahrs.get_yaw_rad())) <= radians(SAILBOAT_TACKING_ACCURACY_DEG)) {
     486           8 :             clear_tack();
     487         786 :         } else if ((now - auto_tack_start_ms) > SAILBOAT_AUTO_TACKING_TIMEOUT_MS) {
     488             :             // tack has taken too long
     489           0 :             if ((motor_state == UseMotor::USE_MOTOR_ASSIST) && (now - auto_tack_start_ms) < (3.0f * SAILBOAT_AUTO_TACKING_TIMEOUT_MS)) {
     490             :                 // if we have throttle available use it for another two time periods to get the tack done
     491           0 :                 tack_assist = true;
     492             :             } else {
     493           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Sailboat: Tacking timed out");
     494           0 :                 clear_tack();
     495             :             }
     496             :         }
     497             :         // return tack target heading
     498         794 :         return degrees(tack_heading_rad) * 100.0f;
     499             :     }
     500             : 
     501             :     // return the correct heading for our current tack
     502        1940 :     if (current_tack == AP_WindVane::Sailboat_Tack::TACK_PORT) {
     503         700 :         return degrees(left_no_go_heading_rad) * 100.0f;
     504             :     } else {
     505        1240 :         return degrees(right_no_go_heading_rad) * 100.0f;
     506             :     }
     507             : }
     508             : 
     509             : // set state of motor
     510         285 : void Sailboat::set_motor_state(UseMotor state, bool report_failure)
     511             : {
     512             :     // always allow motor to be disabled
     513         285 :     if (state == UseMotor::USE_MOTOR_NEVER) {
     514           1 :         motor_state = state;
     515           1 :         return;
     516             :     }
     517             : 
     518             :     // enable assistance or always on if a motor is defined
     519         532 :     if (rover.g2.motors.have_skid_steering() ||
     520         532 :         SRV_Channels::function_assigned(SRV_Channel::k_throttle) ||
     521           0 :         rover.get_frame_type() != rover.g2.motors.frame_type::FRAME_TYPE_UNDEFINED) {
     522         284 :         motor_state = state;
     523           0 :     } else if (report_failure) {
     524           0 :         GCS_SEND_TEXT(MAV_SEVERITY_WARNING, "Sailboat: failed to enable motor");
     525             :     }
     526             : }
     527             : 
     528             : // true if motor is on to assist with slow tack
     529       17468 : bool Sailboat::motor_assist_tack() const
     530             : {
     531             :     // throttle is assist is disabled
     532       17468 :     if (motor_state != UseMotor::USE_MOTOR_ASSIST) {
     533           0 :         return false;
     534             :     }
     535             : 
     536             :     // assist with a tack because it is taking too long
     537       17468 :     return tack_assist;
     538             : }
     539             : 
     540             : // true if motor should be on to assist with low wind
     541       60445 : bool Sailboat::motor_assist_low_wind() const
     542             : {
     543             :     // motor assist is disabled
     544       60445 :     if (motor_state != UseMotor::USE_MOTOR_ASSIST) {
     545           4 :         return false;
     546             :     }
     547             : 
     548             :     // assist if wind speed is below cutoff
     549       60441 :     return (is_positive(sail_windspeed_min) &&
     550       60441 :             rover.g2.windvane.wind_speed_enabled() &&
     551       60441 :             (rover.g2.windvane.get_true_wind_speed() < sail_windspeed_min));
     552             : }

Generated by: LCOV version 1.14