|
|
|
@ -1,5 +1,7 @@
@@ -1,5 +1,7 @@
|
|
|
|
|
#include "Rover.h" |
|
|
|
|
|
|
|
|
|
#define SAILBOAT_AUTO_TACKING_TIMEOUT_MS 50000 // tacks in auto mode timeout if not successfully completed within this many milliseconds
|
|
|
|
|
#define SAILBOAT_TACKING_ACCURACY_DEG 10 // tack is considered complete when vehicle is within this many degrees of target tack angle
|
|
|
|
|
/*
|
|
|
|
|
To Do List |
|
|
|
|
- Improve tacking in light winds and bearing away in strong wings |
|
|
|
@ -67,3 +69,131 @@ float Rover::sailboat_get_VMG() const
@@ -67,3 +69,131 @@ float Rover::sailboat_get_VMG() const
|
|
|
|
|
} |
|
|
|
|
return (speed * cosf(wrap_PI(radians(nav_controller->target_bearing_cd()) - ahrs.yaw))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// handle user initiated tack while in acro mode
|
|
|
|
|
void Rover::sailboat_handle_tack_request_acro() |
|
|
|
|
{ |
|
|
|
|
// set tacking heading target to the current angle relative to the true wind but on the new tack
|
|
|
|
|
_sailboat_tacking = true; |
|
|
|
|
_sailboat_tack_heading_rad = wrap_2PI(ahrs.yaw + 2.0f * wrap_PI((g2.windvane.get_absolute_wind_direction_rad() - ahrs.yaw))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// return target heading in radians when tacking (only used in acro)
|
|
|
|
|
float Rover::sailboat_get_tack_heading_rad() const |
|
|
|
|
{ |
|
|
|
|
return _sailboat_tack_heading_rad; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// handle user initiated tack while in autonomous modes (Auto, Guided, RTL, SmartRTL)
|
|
|
|
|
void Rover::sailboat_handle_tack_request_auto() |
|
|
|
|
{ |
|
|
|
|
// record time of request for tack. This will be processed asynchronously by sailboat_calc_heading
|
|
|
|
|
_sailboat_auto_tack_request_ms = AP_HAL::millis(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// clear tacking state variables
|
|
|
|
|
void Rover::sailboat_clear_tack() |
|
|
|
|
{ |
|
|
|
|
_sailboat_tacking = false; |
|
|
|
|
_sailboat_auto_tack_request_ms = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// returns true if boat is currently tacking
|
|
|
|
|
bool Rover::sailboat_tacking() const |
|
|
|
|
{ |
|
|
|
|
return _sailboat_tacking; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// returns true if sailboat should take a indirect navigation route to go upwind
|
|
|
|
|
// desired_heading should be in centi-degrees
|
|
|
|
|
bool Rover::sailboat_use_indirect_route(float desired_heading_cd) const |
|
|
|
|
{ |
|
|
|
|
if (!g2.motors.has_sail()) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// convert desired heading to radians
|
|
|
|
|
const float desired_heading_rad = radians(desired_heading_cd * 0.01f); |
|
|
|
|
|
|
|
|
|
// check if desired heading is in the no go zone, if it is we can't go direct
|
|
|
|
|
return fabsf(wrap_PI(g2.windvane.get_absolute_wind_direction_rad() - desired_heading_rad)) <= radians(g2.sail_no_go); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// if we can't sail on the desired heading then we should pick the best heading that we can sail on
|
|
|
|
|
// this function assumes the caller has already checked sailboat_use_indirect_route(desired_heading_cd) returned true
|
|
|
|
|
float Rover::sailboat_calc_heading(float desired_heading_cd) |
|
|
|
|
{ |
|
|
|
|
if (!g2.motors.has_sail()) { |
|
|
|
|
return desired_heading_cd; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
bool should_tack = false; |
|
|
|
|
|
|
|
|
|
// check for user requested tack
|
|
|
|
|
uint32_t now = AP_HAL::millis(); |
|
|
|
|
if (_sailboat_auto_tack_request_ms != 0) { |
|
|
|
|
// set should_tack flag is user requested tack within last 0.5 sec
|
|
|
|
|
should_tack = ((now - _sailboat_auto_tack_request_ms) < 500); |
|
|
|
|
_sailboat_auto_tack_request_ms = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// calculate left and right no go headings looking upwind
|
|
|
|
|
const float left_no_go_heading_rad = wrap_2PI(g2.windvane.get_absolute_wind_direction_rad() + radians(g2.sail_no_go)); |
|
|
|
|
const float right_no_go_heading_rad = wrap_2PI(g2.windvane.get_absolute_wind_direction_rad() - radians(g2.sail_no_go)); |
|
|
|
|
|
|
|
|
|
// calculate current tack, Port if heading is left of no-go, STBD if right of no-go
|
|
|
|
|
Sailboat_Tack current_tack; |
|
|
|
|
if (is_negative(g2.windvane.get_apparent_wind_direction_rad())) { |
|
|
|
|
current_tack = Tack_Port; |
|
|
|
|
} else { |
|
|
|
|
current_tack = Tack_STBD; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// trigger tack if cross track error larger than waypoint_overshoot parameter
|
|
|
|
|
// this effectively defines a 'corridor' of width 2*waypoint_overshoot that the boat will stay within
|
|
|
|
|
if ((fabsf(rover.nav_controller->crosstrack_error()) >= g.waypoint_overshoot) && !is_zero(g.waypoint_overshoot) && !sailboat_tacking()) { |
|
|
|
|
// make sure the new tack will reduce the cross track error
|
|
|
|
|
// if were on starboard tack we are traveling towards the left hand boundary
|
|
|
|
|
if (is_positive(rover.nav_controller->crosstrack_error()) && (current_tack == Tack_STBD)) { |
|
|
|
|
should_tack = true; |
|
|
|
|
} |
|
|
|
|
// if were on port tack we are traveling towards the right hand boundary
|
|
|
|
|
if (is_negative(rover.nav_controller->crosstrack_error()) && (current_tack == Tack_Port)) { |
|
|
|
|
should_tack = true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// if tack triggered, calculate target heading
|
|
|
|
|
if (should_tack) { |
|
|
|
|
gcs().send_text(MAV_SEVERITY_INFO, "Sailboat: Tacking"); |
|
|
|
|
// calculate target heading for the new tack
|
|
|
|
|
switch (current_tack) { |
|
|
|
|
case Tack_Port: |
|
|
|
|
_sailboat_tack_heading_rad = right_no_go_heading_rad; |
|
|
|
|
break; |
|
|
|
|
case Tack_STBD: |
|
|
|
|
_sailboat_tack_heading_rad = left_no_go_heading_rad; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
_sailboat_tacking = true; |
|
|
|
|
_sailboat_auto_tack_start_ms = AP_HAL::millis(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// if were are tacking we maintain the target heading until the tack completes or timesout
|
|
|
|
|
if (_sailboat_tacking) { |
|
|
|
|
// if we have reached target heading or timed out stop tacking on the next iteration
|
|
|
|
|
if (((now - _sailboat_auto_tack_start_ms) > SAILBOAT_AUTO_TACKING_TIMEOUT_MS) || |
|
|
|
|
(fabsf(wrap_PI(_sailboat_tack_heading_rad - ahrs.yaw)) <= radians(SAILBOAT_TACKING_ACCURACY_DEG))) { |
|
|
|
|
_sailboat_tacking = false; |
|
|
|
|
} |
|
|
|
|
// return tack target heading
|
|
|
|
|
return degrees(_sailboat_tack_heading_rad) * 100.0f; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// return closest possible heading to wind
|
|
|
|
|
if (current_tack == Tack_Port) { |
|
|
|
|
return degrees(left_no_go_heading_rad) * 100.0f; |
|
|
|
|
} else { |
|
|
|
|
return degrees(right_no_go_heading_rad) * 100.0f; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|