ardupilot/libraries/AC_WPNav/AC_WPNav.cpp
Andrew Tridgell adc2bf1955 AC_WPNav: cope with negative WPNAV_SPEED_DN
a user set WPNAV_SPEED_DN to a negative value, with odd results. Take
absolute value to cope. Even though the param docs clearly say range
should be positive, it is one where it is easy to think it should be
negative
2021-09-10 14:07:37 +09:00

873 lines
36 KiB
C++

#include <AP_HAL/AP_HAL.h>
#include "AC_WPNav.h"
extern const AP_HAL::HAL& hal;
// maximum velocities and accelerations
#define WPNAV_ACCELERATION 250.0f // maximum horizontal acceleration in cm/s/s that wp navigation will request
#define WPNAV_WP_SPEED 1000.0f // default horizontal speed between waypoints in cm/s
#define WPNAV_WP_SPEED_MIN 20.0f // minimum horizontal speed between waypoints in cm/s
#define WPNAV_WP_RADIUS 200.0f // default waypoint radius in cm
#define WPNAV_WP_RADIUS_MIN 5.0f // minimum waypoint radius in cm
#define WPNAV_WP_SPEED_UP 250.0f // default maximum climb velocity
#define WPNAV_WP_SPEED_DOWN 150.0f // default maximum descent velocity
#define WPNAV_WP_ACCEL_Z_DEFAULT 100.0f // default vertical acceleration between waypoints in cm/s/s
const AP_Param::GroupInfo AC_WPNav::var_info[] = {
// index 0 was used for the old orientation matrix
// @Param: SPEED
// @DisplayName: Waypoint Horizontal Speed Target
// @Description: Defines the speed in cm/s which the aircraft will attempt to maintain horizontally during a WP mission
// @Units: cm/s
// @Range: 20 2000
// @Increment: 50
// @User: Standard
AP_GROUPINFO("SPEED", 0, AC_WPNav, _wp_speed_cms, WPNAV_WP_SPEED),
// @Param: RADIUS
// @DisplayName: Waypoint Radius
// @Description: Defines the distance from a waypoint, that when crossed indicates the wp has been hit.
// @Units: cm
// @Range: 5 1000
// @Increment: 1
// @User: Standard
AP_GROUPINFO("RADIUS", 1, AC_WPNav, _wp_radius_cm, WPNAV_WP_RADIUS),
// @Param: SPEED_UP
// @DisplayName: Waypoint Climb Speed Target
// @Description: Defines the speed in cm/s which the aircraft will attempt to maintain while climbing during a WP mission
// @Units: cm/s
// @Range: 10 1000
// @Increment: 50
// @User: Standard
AP_GROUPINFO("SPEED_UP", 2, AC_WPNav, _wp_speed_up_cms, WPNAV_WP_SPEED_UP),
// @Param: SPEED_DN
// @DisplayName: Waypoint Descent Speed Target
// @Description: Defines the speed in cm/s which the aircraft will attempt to maintain while descending during a WP mission
// @Units: cm/s
// @Range: 10 500
// @Increment: 10
// @User: Standard
AP_GROUPINFO("SPEED_DN", 3, AC_WPNav, _wp_speed_down_cms, WPNAV_WP_SPEED_DOWN),
// @Param: ACCEL
// @DisplayName: Waypoint Acceleration
// @Description: Defines the horizontal acceleration in cm/s/s used during missions
// @Units: cm/s/s
// @Range: 50 500
// @Increment: 10
// @User: Standard
AP_GROUPINFO("ACCEL", 5, AC_WPNav, _wp_accel_cmss, WPNAV_ACCELERATION),
// @Param: ACCEL_Z
// @DisplayName: Waypoint Vertical Acceleration
// @Description: Defines the vertical acceleration in cm/s/s used during missions
// @Units: cm/s/s
// @Range: 50 500
// @Increment: 10
// @User: Standard
AP_GROUPINFO("ACCEL_Z", 6, AC_WPNav, _wp_accel_z_cmss, WPNAV_WP_ACCEL_Z_DEFAULT),
// @Param: RFND_USE
// @DisplayName: Waypoint missions use rangefinder for terrain following
// @Description: This controls if waypoint missions use rangefinder for terrain following
// @Values: 0:Disable,1:Enable
// @User: Advanced
AP_GROUPINFO("RFND_USE", 10, AC_WPNav, _rangefinder_use, 1),
// @Param: JERK
// @DisplayName: Waypoint Jerk
// @Description: Defines the horizontal jerk in m/s/s used during missions
// @Units: m/s/s/s
// @Range: 1 20
// @User: Standard
AP_GROUPINFO("JERK", 11, AC_WPNav, _wp_jerk, 1.0f),
// @Param: TER_MARGIN
// @DisplayName: Waypoint Terrain following altitude margin
// @Description: Waypoint Terrain following altitude margin. Vehicle will stop if distance from target altitude is larger than this margin (in meters)
// @Units: m
// @Range: 0.1 100
// @User: Advanced
AP_GROUPINFO("TER_MARGIN", 12, AC_WPNav, _terrain_margin, 10.0),
AP_GROUPEND
};
// Default constructor.
// Note that the Vector/Matrix constructors already implicitly zero
// their values.
//
AC_WPNav::AC_WPNav(const AP_InertialNav& inav, const AP_AHRS_View& ahrs, AC_PosControl& pos_control, const AC_AttitudeControl& attitude_control) :
_inav(inav),
_ahrs(ahrs),
_pos_control(pos_control),
_attitude_control(attitude_control)
{
AP_Param::setup_object_defaults(this, var_info);
// init flags
_flags.reached_destination = false;
_flags.fast_waypoint = false;
// initialise old WPNAV_SPEED value
_last_wp_speed_cms = get_default_speed_down();
}
// get expected source of terrain data if alt-above-terrain command is executed (used by Copter's ModeRTL)
AC_WPNav::TerrainSource AC_WPNav::get_terrain_source() const
{
// use range finder if connected
if (_rangefinder_available && _rangefinder_use) {
return AC_WPNav::TerrainSource::TERRAIN_FROM_RANGEFINDER;
}
#if AP_TERRAIN_AVAILABLE
if ((_terrain != nullptr) && _terrain->enabled()) {
return AC_WPNav::TerrainSource::TERRAIN_FROM_TERRAINDATABASE;
} else {
return AC_WPNav::TerrainSource::TERRAIN_UNAVAILABLE;
}
#else
return AC_WPNav::TerrainSource::TERRAIN_UNAVAILABLE;
#endif
}
///
/// waypoint navigation
///
/// wp_and_spline_init - initialise straight line and spline waypoint controllers
/// speed_cms should be a positive value or left at zero to use the default speed
/// updates target roll, pitch targets and I terms based on vehicle lean angles
/// should be called once before the waypoint controller is used but does not need to be called before subsequent updates to destination
void AC_WPNav::wp_and_spline_init(float speed_cms)
{
// sanity check parameters
// check _wp_accel_cmss is reasonable
const float wp_accel_cmss = MIN(_wp_accel_cmss, GRAVITY_MSS * 100.0f * tanf(ToRad(_attitude_control.lean_angle_max() * 0.01f)));
_wp_accel_cmss.set_and_save_ifchanged((_wp_accel_cmss <= 0) ? WPNAV_ACCELERATION : wp_accel_cmss);
// check _wp_radius_cm is reasonable
_wp_radius_cm.set_and_save_ifchanged(MAX(_wp_radius_cm, WPNAV_WP_RADIUS_MIN));
// check _wp_speed
_wp_speed_cms.set_and_save_ifchanged(MAX(_wp_speed_cms, WPNAV_WP_SPEED_MIN));
// initialise position controller
_pos_control.init_z_controller_stopping_point();
_pos_control.init_xy_controller_stopping_point();
// initialize the desired wp speed if not already done
_wp_desired_speed_xy_cms = is_positive(speed_cms) ? speed_cms : _wp_speed_cms;
_wp_desired_speed_xy_cms = MAX(_wp_desired_speed_xy_cms, WPNAV_WP_SPEED_MIN);
// initialise position controller speed and acceleration
_pos_control.set_max_speed_accel_xy(_wp_desired_speed_xy_cms, _wp_accel_cmss);
_pos_control.set_correction_speed_accel_xy(_wp_desired_speed_xy_cms, _wp_accel_cmss);
_pos_control.set_max_speed_accel_z(-get_default_speed_down(), _wp_speed_up_cms, _wp_accel_z_cmss);
_pos_control.set_correction_speed_accel_z(-get_default_speed_down(), _wp_speed_up_cms, _wp_accel_z_cmss);
// calculate scurve jerk and jerk time
if (!is_positive(_wp_jerk)) {
_wp_jerk = _wp_accel_cmss;
}
calc_scurve_jerk_and_jerk_time();
_scurve_prev_leg.init();
_scurve_this_leg.init();
_scurve_next_leg.init();
_track_scalar_dt = 1.0f;
// set flag so get_yaw() returns current heading target
_flags.reached_destination = false;
_flags.fast_waypoint = false;
// initialise origin and destination to stopping point
Vector3f stopping_point;
get_wp_stopping_point(stopping_point);
_origin = _destination = stopping_point;
_terrain_alt = false;
_this_leg_is_spline = false;
// initialise the terrain velocity to the current maximum velocity
_terrain_vel = _wp_desired_speed_xy_cms;
_terrain_accel = 0.0;
}
/// set_speed_xy - allows main code to pass target horizontal velocity for wp navigation
void AC_WPNav::set_speed_xy(float speed_cms)
{
// range check target speed and protect against divide by zero
if (speed_cms >= WPNAV_WP_SPEED_MIN && is_positive(_wp_desired_speed_xy_cms)) {
// update terrain following speed scalar
_terrain_vel = speed_cms * _terrain_vel / _wp_desired_speed_xy_cms;
// initialize the desired wp speed
_wp_desired_speed_xy_cms = speed_cms;
// update position controller speed and acceleration
_pos_control.set_max_speed_accel_xy(_wp_desired_speed_xy_cms, _wp_accel_cmss);
_pos_control.set_correction_speed_accel_xy(_wp_desired_speed_xy_cms, _wp_accel_cmss);
// change track speed
update_track_with_speed_accel_limits();
}
}
/// set current target climb rate during wp navigation
void AC_WPNav::set_speed_up(float speed_up_cms)
{
_pos_control.set_max_speed_accel_z(_pos_control.get_max_speed_down_cms(), speed_up_cms, _pos_control.get_max_accel_z_cmss());
update_track_with_speed_accel_limits();
}
/// set current target descent rate during wp navigation
void AC_WPNav::set_speed_down(float speed_down_cms)
{
_pos_control.set_max_speed_accel_z(speed_down_cms, _pos_control.get_max_speed_up_cms(), _pos_control.get_max_accel_z_cmss());
update_track_with_speed_accel_limits();
}
/// set_wp_destination waypoint using location class
/// returns false if conversion from location to vector from ekf origin cannot be calculated
bool AC_WPNav::set_wp_destination_loc(const Location& destination)
{
bool terr_alt;
Vector3f dest_neu;
// convert destination location to vector
if (!get_vector_NEU(destination, dest_neu, terr_alt)) {
return false;
}
// set target as vector from EKF origin
return set_wp_destination(dest_neu, terr_alt);
}
/// set next destination using location class
/// returns false if conversion from location to vector from ekf origin cannot be calculated
bool AC_WPNav::set_wp_destination_next_loc(const Location& destination)
{
bool terr_alt;
Vector3f dest_neu;
// convert destination location to vector
if (!get_vector_NEU(destination, dest_neu, terr_alt)) {
return false;
}
// set target as vector from EKF origin
return set_wp_destination_next(dest_neu, terr_alt);
}
// get destination as a location. Altitude frame will be above origin or above terrain
// returns false if unable to return a destination (for example if origin has not yet been set)
bool AC_WPNav::get_wp_destination_loc(Location& destination) const
{
if (!AP::ahrs().get_origin(destination)) {
return false;
}
destination = Location{get_wp_destination(), _terrain_alt ? Location::AltFrame::ABOVE_TERRAIN : Location::AltFrame::ABOVE_ORIGIN};
return true;
}
/// set_wp_destination - set destination waypoints using position vectors (distance from ekf origin in cm)
/// terrain_alt should be true if destination.z is an altitude above terrain (false if alt-above-ekf-origin)
/// returns false on failure (likely caused by missing terrain data)
bool AC_WPNav::set_wp_destination(const Vector3f& destination, bool terrain_alt)
{
// re-initialise if previous destination has been interrupted
if (!is_active() || !_flags.reached_destination) {
wp_and_spline_init(_wp_desired_speed_xy_cms);
}
_scurve_prev_leg.init();
float origin_speed = 0.0f;
// use previous destination as origin
_origin = _destination;
if (terrain_alt == _terrain_alt) {
if (_this_leg_is_spline) {
// if previous leg was a spline we can use current target velocity vector for origin velocity vector
Vector3f curr_target_vel = _pos_control.get_vel_desired_cms();
curr_target_vel.z -= _pos_control.get_vel_offset_z_cms();
origin_speed = curr_target_vel.length();
} else {
// store previous leg
_scurve_prev_leg = _scurve_this_leg;
}
} else {
// get current alt above terrain
float origin_terr_offset;
if (!get_terrain_offset(origin_terr_offset)) {
return false;
}
// convert origin to alt-above-terrain if necessary
if (terrain_alt) {
// new destination is alt-above-terrain, previous destination was alt-above-ekf-origin
_origin.z -= origin_terr_offset;
_pos_control.set_pos_offset_z_cm(_pos_control.get_pos_offset_z_cm() + origin_terr_offset);
} else {
// new destination is alt-above-ekf-origin, previous destination was alt-above-terrain
_origin.z += origin_terr_offset;
_pos_control.set_pos_offset_z_cm(_pos_control.get_pos_offset_z_cm() - origin_terr_offset);
}
}
// update destination
_destination = destination;
_terrain_alt = terrain_alt;
if (_flags.fast_waypoint && !_this_leg_is_spline && !_next_leg_is_spline && !_scurve_next_leg.finished()) {
_scurve_this_leg = _scurve_next_leg;
} else {
_scurve_this_leg.calculate_track(_origin, _destination,
_pos_control.get_max_speed_xy_cms(), _pos_control.get_max_speed_up_cms(), _pos_control.get_max_speed_down_cms(),
_wp_accel_cmss, _wp_accel_z_cmss,
_scurve_jerk_time, _scurve_jerk * 100.0f);
if (!is_zero(origin_speed)) {
// rebuild start of scurve if we have a non-zero origin speed
_scurve_this_leg.set_origin_speed_max(origin_speed);
}
}
_this_leg_is_spline = false;
_scurve_next_leg.init();
_flags.fast_waypoint = false; // default waypoint back to slow
_flags.reached_destination = false;
return true;
}
/// set next destination using position vector (distance from ekf origin in cm)
/// terrain_alt should be true if destination.z is a desired altitude above terrain
/// provide next_destination
bool AC_WPNav::set_wp_destination_next(const Vector3f& destination, bool terrain_alt)
{
// do not add next point if alt types don't match
if (terrain_alt != _terrain_alt) {
return true;
}
_scurve_next_leg.calculate_track(_destination, destination,
_pos_control.get_max_speed_xy_cms(), _pos_control.get_max_speed_up_cms(), _pos_control.get_max_speed_down_cms(),
_wp_accel_cmss, _wp_accel_z_cmss,
_scurve_jerk_time, _scurve_jerk * 100.0f);
if (_this_leg_is_spline) {
const float this_leg_dest_speed_max = _spline_this_leg.get_destination_speed_max();
const float next_leg_origin_speed_max = _scurve_next_leg.set_origin_speed_max(this_leg_dest_speed_max);
_spline_this_leg.set_destination_speed_max(next_leg_origin_speed_max);
}
_next_leg_is_spline = false;
// next destination provided so fast waypoint
_flags.fast_waypoint = true;
return true;
}
/// set waypoint destination using NED position vector from ekf origin in meters
bool AC_WPNav::set_wp_destination_NED(const Vector3f& destination_NED)
{
// convert NED to NEU and do not use terrain following
return set_wp_destination(Vector3f(destination_NED.x * 100.0f, destination_NED.y * 100.0f, -destination_NED.z * 100.0f), false);
}
/// set waypoint destination using NED position vector from ekf origin in meters
bool AC_WPNav::set_wp_destination_next_NED(const Vector3f& destination_NED)
{
// convert NED to NEU and do not use terrain following
return set_wp_destination_next(Vector3f(destination_NED.x * 100.0f, destination_NED.y * 100.0f, -destination_NED.z * 100.0f), false);
}
/// shifts the origin and destination horizontally to the current position
/// used to reset the track when taking off without horizontal position control
/// relies on set_wp_destination or set_wp_origin_and_destination having been called first
void AC_WPNav::shift_wp_origin_and_destination_to_current_pos_xy()
{
// Reset position controller to current location
_pos_control.init_xy_controller();
// get current and target locations
const Vector3f& curr_pos = _inav.get_position();
// shift origin and destination horizontally
_origin.x = curr_pos.x;
_origin.y = curr_pos.y;
_destination.x = curr_pos.x;
_destination.y = curr_pos.y;
}
/// shifts the origin and destination horizontally to the achievable stopping point
/// used to reset the track when horizontal navigation is enabled after having been disabled (see Copter's wp_navalt_min)
/// relies on set_wp_destination or set_wp_origin_and_destination having been called first
void AC_WPNav::shift_wp_origin_and_destination_to_stopping_point_xy()
{
// relax position control in xy axis
// removing velocity error also impacts stopping point calculation
_pos_control.relax_velocity_controller_xy();
// get current and target locations
Vector2f stopping_point;
get_wp_stopping_point_xy(stopping_point);
// shift origin and destination horizontally
_origin.xy() = stopping_point;
_destination.xy() = stopping_point;
// move pos controller target horizontally
_pos_control.set_pos_target_xy_cm(stopping_point.x, stopping_point.y);
}
/// get_wp_stopping_point_xy - returns vector to stopping point based on a horizontal position and velocity
void AC_WPNav::get_wp_stopping_point_xy(Vector2f& stopping_point) const
{
Vector2p stop;
_pos_control.get_stopping_point_xy_cm(stop);
stopping_point = stop.tofloat();
}
/// get_wp_stopping_point - returns vector to stopping point based on 3D position and velocity
void AC_WPNav::get_wp_stopping_point(Vector3f& stopping_point) const
{
Vector3p stop;
_pos_control.get_stopping_point_xy_cm(stop.xy());
_pos_control.get_stopping_point_z_cm(stop.z);
stopping_point = stop.tofloat();
}
/// advance_wp_target_along_track - move target location along track from origin to destination
bool AC_WPNav::advance_wp_target_along_track(float dt)
{
// calculate terrain adjustments
float terr_offset = 0.0f;
if (_terrain_alt && !get_terrain_offset(terr_offset)) {
return false;
}
const float offset_z_scaler = _pos_control.pos_offset_z_scaler(terr_offset, get_terrain_margin() * 100.0);
// input shape the terrain offset
_pos_control.update_pos_offset_z(terr_offset);
// get current position and adjust altitude to origin and destination's frame (i.e. _frame)
const Vector3f &curr_pos = _inav.get_position() - Vector3f{0, 0, terr_offset};
// Use _track_scalar_dt to slow down S-Curve time to prevent target moving too far in front of aircraft
Vector3f curr_target_vel = _pos_control.get_vel_desired_cms();
curr_target_vel.z -= _pos_control.get_vel_offset_z_cms();
float track_scaler_dt = 1.0f;
// check target velocity is non-zero
if (is_positive(curr_target_vel.length())) {
Vector3f track_direction = curr_target_vel.normalized();
const float track_error = _pos_control.get_pos_error_cm().dot(track_direction);
const float track_velocity = _inav.get_velocity().dot(track_direction);
// set time scaler to be consistent with the achievable aircraft speed with a 5% buffer for short term variation.
track_scaler_dt = constrain_float(0.05f + (track_velocity - _pos_control.get_pos_xy_p().kP() * track_error) / curr_target_vel.length(), 0.1f, 1.0f);
}
float vel_time_scalar = 1.0;
if (is_positive(_wp_desired_speed_xy_cms)) {
update_vel_accel(_terrain_vel, _terrain_accel, dt, false);
shape_vel_accel( _wp_desired_speed_xy_cms * offset_z_scaler, 0.0,
_terrain_vel, _terrain_accel,
-_wp_accel_cmss, _wp_accel_cmss, _pos_control.get_shaping_jerk_xy_cmsss(), dt, true);
vel_time_scalar = _terrain_vel / _wp_desired_speed_xy_cms;
}
// change s-curve time speed with a time constant of maximum acceleration / maximum jerk
float track_scaler_tc = 1.0f;
if (!is_zero(_wp_jerk)) {
track_scaler_tc = 0.01f * _wp_accel_cmss/_wp_jerk;
}
_track_scalar_dt += (track_scaler_dt - _track_scalar_dt) * (dt / track_scaler_tc);
// target position, velocity and acceleration from straight line or spline calculators
Vector3f target_pos, target_vel, target_accel;
bool s_finished;
if (!_this_leg_is_spline) {
// update target position, velocity and acceleration
target_pos = _origin;
s_finished = _scurve_this_leg.advance_target_along_track(_scurve_prev_leg, _scurve_next_leg, _wp_radius_cm, _flags.fast_waypoint, _track_scalar_dt * vel_time_scalar * dt, target_pos, target_vel, target_accel);
} else {
// splinetarget_vel
target_vel = curr_target_vel;
_spline_this_leg.advance_target_along_track(_track_scalar_dt * vel_time_scalar * dt, target_pos, target_vel);
s_finished = _spline_this_leg.reached_destination();
}
target_vel *= vel_time_scalar;
target_accel *= sq(vel_time_scalar);
// convert final_target.z to altitude above the ekf origin
target_pos.z += _pos_control.get_pos_offset_z_cm();
target_vel.z += _pos_control.get_vel_offset_z_cms();
target_accel.z += _pos_control.get_accel_offset_z_cmss();
// pass new target to the position controller
_pos_control.set_pos_vel_accel(target_pos.topostype(), target_vel, target_accel);
// check if we've reached the waypoint
if (!_flags.reached_destination) {
if (s_finished) {
// "fast" waypoints are complete once the intermediate point reaches the destination
if (_flags.fast_waypoint) {
_flags.reached_destination = true;
} else {
// regular waypoints also require the copter to be within the waypoint radius
const Vector3f dist_to_dest = curr_pos - _destination;
if (dist_to_dest.length_squared() <= sq(_wp_radius_cm)) {
_flags.reached_destination = true;
}
}
}
}
// successfully advanced along track
return true;
}
/// recalculate path with update speed and/or acceleration limits
void AC_WPNav::update_track_with_speed_accel_limits()
{
// update this leg
if (_this_leg_is_spline) {
_spline_this_leg.set_speed_accel(_pos_control.get_max_speed_xy_cms(), _pos_control.get_max_speed_up_cms(), _pos_control.get_max_speed_down_cms(),
_wp_accel_cmss, _wp_accel_z_cmss);
} else {
_scurve_this_leg.set_speed_max(_pos_control.get_max_speed_xy_cms(), _pos_control.get_max_speed_up_cms(), _pos_control.get_max_speed_down_cms());
}
// update next leg
if (_next_leg_is_spline) {
_spline_next_leg.set_speed_accel(_pos_control.get_max_speed_xy_cms(), _pos_control.get_max_speed_up_cms(), _pos_control.get_max_speed_down_cms(),
_wp_accel_cmss, _wp_accel_z_cmss);
} else {
_scurve_next_leg.set_speed_max(_pos_control.get_max_speed_xy_cms(), _pos_control.get_max_speed_up_cms(), _pos_control.get_max_speed_down_cms());
}
}
/// get_wp_distance_to_destination - get horizontal distance to destination in cm
float AC_WPNav::get_wp_distance_to_destination() const
{
// get current location
const Vector3f &curr = _inav.get_position();
return norm(_destination.x-curr.x,_destination.y-curr.y);
}
/// get_wp_bearing_to_destination - get bearing to next waypoint in centi-degrees
int32_t AC_WPNav::get_wp_bearing_to_destination() const
{
return get_bearing_cd(_inav.get_position(), _destination);
}
/// update_wpnav - run the wp controller - should be called at 100hz or higher
bool AC_WPNav::update_wpnav()
{
bool ret = true;
if (!is_equal(_wp_speed_cms.get(), _last_wp_speed_cms)) {
set_speed_xy(_wp_speed_cms);
_last_wp_speed_cms = _wp_speed_cms;
}
// advance the target if necessary
if (!advance_wp_target_along_track(_pos_control.get_dt())) {
// To-Do: handle inability to advance along track (probably because of missing terrain data)
ret = false;
}
_pos_control.update_xy_controller();
_wp_last_update = AP_HAL::millis();
return ret;
}
// returns true if update_wpnav has been run very recently
bool AC_WPNav::is_active() const
{
return (AP_HAL::millis() - _wp_last_update) < 200;
}
// get terrain's altitude (in cm above the ekf origin) at the current position (+ve means terrain below vehicle is above ekf origin's altitude)
bool AC_WPNav::get_terrain_offset(float& offset_cm)
{
// calculate offset based on source (rangefinder or terrain database)
switch (get_terrain_source()) {
case AC_WPNav::TerrainSource::TERRAIN_UNAVAILABLE:
return false;
case AC_WPNav::TerrainSource::TERRAIN_FROM_RANGEFINDER:
if (_rangefinder_healthy) {
offset_cm = _inav.get_altitude() - _rangefinder_alt_cm;
return true;
}
return false;
case AC_WPNav::TerrainSource::TERRAIN_FROM_TERRAINDATABASE:
#if AP_TERRAIN_AVAILABLE
float terr_alt = 0.0f;
if (_terrain != nullptr && _terrain->height_above_terrain(terr_alt, true)) {
offset_cm = _inav.get_altitude() - (terr_alt * 100.0f);
return true;
}
#endif
return false;
}
// we should never get here
return false;
}
///
/// spline methods
///
/// set_spline_destination waypoint using location class
/// returns false if conversion from location to vector from ekf origin cannot be calculated
/// next_destination should be the next segment's destination
/// next_is_spline should be true if path to next_destination should be a spline
bool AC_WPNav::set_spline_destination_loc(const Location& destination, const Location& next_destination, bool next_is_spline)
{
// convert destination location to vector
Vector3f dest_neu;
bool dest_terr_alt;
if (!get_vector_NEU(destination, dest_neu, dest_terr_alt)) {
return false;
}
// convert next destination to vector
Vector3f next_dest_neu;
bool next_dest_terr_alt;
if (!get_vector_NEU(next_destination, next_dest_neu, next_dest_terr_alt)) {
return false;
}
// set target as vector from EKF origin
return set_spline_destination(dest_neu, dest_terr_alt, next_dest_neu, next_dest_terr_alt, next_is_spline);
}
/// set next destination (e.g. the one after the current destination) as a spline segment specified as a location
/// returns false if conversion from location to vector from ekf origin cannot be calculated
/// next_next_destination should be the next segment's destination
bool AC_WPNav::set_spline_destination_next_loc(const Location& next_destination, const Location& next_next_destination, bool next_next_is_spline)
{
// convert next_destination location to vector
Vector3f next_dest_neu;
bool next_dest_terr_alt;
if (!get_vector_NEU(next_destination, next_dest_neu, next_dest_terr_alt)) {
return false;
}
// convert next_next_destination to vector
Vector3f next_next_dest_neu;
bool next_next_dest_terr_alt;
if (!get_vector_NEU(next_next_destination, next_next_dest_neu, next_next_dest_terr_alt)) {
return false;
}
// set target as vector from EKF origin
return set_spline_destination_next(next_dest_neu, next_dest_terr_alt, next_next_dest_neu, next_next_dest_terr_alt, next_next_is_spline);
}
/// set_spline_destination waypoint using position vector (distance from ekf origin in cm)
/// terrain_alt should be true if destination.z is a desired altitude above terrain (false if its desired altitudes above ekf origin)
/// next_destination should be set to the next segment's destination
/// next_terrain_alt should be true if next_destination.z is a desired altitude above terrain (false if its desired altitudes above ekf origin)
/// next_destination.z must be in the same "frame" as destination.z (i.e. if destination is a alt-above-terrain, next_destination should be too)
bool AC_WPNav::set_spline_destination(const Vector3f& destination, bool terrain_alt, const Vector3f& next_destination, bool next_terrain_alt, bool next_is_spline)
{
// re-initialise if previous destination has been interrupted
if (!is_active() || !_flags.reached_destination) {
wp_and_spline_init(_wp_desired_speed_xy_cms);
}
// update spline calculators speeds and accelerations
_spline_this_leg.set_speed_accel(_pos_control.get_max_speed_xy_cms(), _pos_control.get_max_speed_up_cms(), _pos_control.get_max_speed_down_cms(),
_pos_control.get_max_accel_xy_cmss(), _pos_control.get_max_accel_z_cmss());
// calculate origin and origin velocity vector
Vector3f origin_vector;
if (terrain_alt == _terrain_alt) {
if (_flags.fast_waypoint) {
// calculate origin vector
if (_this_leg_is_spline) {
// if previous leg was a spline we can use destination velocity vector for origin velocity vector
origin_vector = _spline_this_leg.get_destination_vel();
} else {
// use direction of the previous straight line segment
origin_vector = _destination - _origin;
}
}
// use previous destination as origin
_origin = _destination;
} else {
// use previous destination as origin
_origin = _destination;
// get current alt above terrain
float origin_terr_offset;
if (!get_terrain_offset(origin_terr_offset)) {
return false;
}
// convert origin to alt-above-terrain if necessary
if (terrain_alt) {
// new destination is alt-above-terrain, previous destination was alt-above-ekf-origin
_origin.z -= origin_terr_offset;
_pos_control.set_pos_offset_z_cm(_pos_control.get_pos_offset_z_cm() + origin_terr_offset);
} else {
// new destination is alt-above-ekf-origin, previous destination was alt-above-terrain
_origin.z += origin_terr_offset;
_pos_control.set_pos_offset_z_cm(_pos_control.get_pos_offset_z_cm() - origin_terr_offset);
}
}
// store destination location
_destination = destination;
_terrain_alt = terrain_alt;
// calculate destination velocity vector
Vector3f destination_vector;
if (terrain_alt == next_terrain_alt) {
if (next_is_spline) {
// leave this segment moving parallel to vector from origin to next destination
destination_vector = next_destination - _origin;
} else {
// leave this segment moving parallel to next segment
destination_vector = next_destination - _destination;
}
}
_flags.fast_waypoint = !destination_vector.is_zero();
// setup spline leg
_spline_this_leg.set_origin_and_destination(_origin, _destination, origin_vector, destination_vector);
_this_leg_is_spline = true;
_flags.reached_destination = false;
return true;
}
/// set next destination (e.g. the one after the current destination) as an offset (in cm, NEU frame) from the EKF origin
/// next_terrain_alt should be true if next_destination.z is a desired altitude above terrain (false if its desired altitudes above ekf origin)
/// next_next_destination should be set to the next segment's destination
/// next_next_terrain_alt should be true if next_next_destination.z is a desired altitude above terrain (false if it is desired altitude above ekf origin)
/// next_next_destination.z must be in the same "frame" as destination.z (i.e. if next_destination is a alt-above-terrain, next_next_destination should be too)
bool AC_WPNav::set_spline_destination_next(const Vector3f& next_destination, bool next_terrain_alt, const Vector3f& next_next_destination, bool next_next_terrain_alt, bool next_next_is_spline)
{
// do not add next point if alt types don't match
if (next_terrain_alt != _terrain_alt) {
return true;
}
// calculate origin and origin velocity vector
Vector3f origin_vector;
if (_this_leg_is_spline) {
// if previous leg was a spline we can use destination velocity vector for origin velocity vector
origin_vector = _spline_this_leg.get_destination_vel();
} else {
// use the direction of the previous straight line segment
origin_vector = _destination - _origin;
}
// calculate destination velocity vector
Vector3f destination_vector;
if (next_terrain_alt == next_next_terrain_alt) {
if (next_next_is_spline) {
// leave this segment moving parallel to vector from this leg's origin (i.e. prev leg's destination) to next next destination
destination_vector = next_next_destination - _destination;
} else {
// leave this segment moving parallel to next segment
destination_vector = next_next_destination - next_destination;
}
}
// update spline calculators speeds and accelerations
_spline_next_leg.set_speed_accel(_pos_control.get_max_speed_xy_cms(), _pos_control.get_max_speed_up_cms(), _pos_control.get_max_speed_down_cms(),
_pos_control.get_max_accel_xy_cmss(), _pos_control.get_max_accel_z_cmss());
// setup next spline leg. Note this could be made local
_spline_next_leg.set_origin_and_destination(_destination, next_destination, origin_vector, destination_vector);
_next_leg_is_spline = true;
// next destination provided so fast waypoint
_flags.fast_waypoint = true;
// update this_leg's final velocity to match next spline leg
if (!_this_leg_is_spline) {
_scurve_this_leg.set_destination_speed_max(_spline_next_leg.get_origin_speed_max());
} else {
_spline_this_leg.set_destination_speed_max(_spline_next_leg.get_origin_speed_max());
}
return true;
}
// convert location to vector from ekf origin. terrain_alt is set to true if resulting vector's z-axis should be treated as alt-above-terrain
// returns false if conversion failed (likely because terrain data was not available)
bool AC_WPNav::get_vector_NEU(const Location &loc, Vector3f &vec, bool &terrain_alt)
{
// convert location to NE vector2f
Vector2f res_vec;
if (!loc.get_vector_xy_from_origin_NE(res_vec)) {
return false;
}
// convert altitude
if (loc.get_alt_frame() == Location::AltFrame::ABOVE_TERRAIN) {
int32_t terr_alt;
if (!loc.get_alt_cm(Location::AltFrame::ABOVE_TERRAIN, terr_alt)) {
return false;
}
vec.z = terr_alt;
terrain_alt = true;
} else {
terrain_alt = false;
int32_t temp_alt;
if (!loc.get_alt_cm(Location::AltFrame::ABOVE_ORIGIN, temp_alt)) {
return false;
}
vec.z = temp_alt;
terrain_alt = false;
}
// copy xy (we do this to ensure we do not adjust vector unless the overall conversion is successful
vec.x = res_vec.x;
vec.y = res_vec.y;
return true;
}
// helper function to calculate scurve jerk and jerk_time values
// updates _scurve_jerk and _scurve_jerk_time
void AC_WPNav::calc_scurve_jerk_and_jerk_time()
{
// calculate jerk
_scurve_jerk = MIN(_attitude_control.get_ang_vel_roll_max_rads() * GRAVITY_MSS, _attitude_control.get_ang_vel_pitch_max_rads() * GRAVITY_MSS);
if (is_zero(_scurve_jerk)) {
_scurve_jerk = _wp_jerk;
} else {
_scurve_jerk = MIN(_scurve_jerk, _wp_jerk);
}
// calculate jerk time
// jounce (the rate of change of jerk) uses the attitude control input time constant because multicopters
// lean to accelerate meaning the change in angle is equivalent to the change in acceleration
const float jounce = MIN(_attitude_control.get_accel_roll_max() * GRAVITY_MSS, _attitude_control.get_accel_pitch_max() * GRAVITY_MSS);
if (is_positive(jounce)) {
_scurve_jerk_time = MAX(_attitude_control.get_input_tc(), 0.5f * _scurve_jerk * M_PI / jounce);
} else {
_scurve_jerk_time = MAX(_attitude_control.get_input_tc(), 0.1f);
}
_scurve_jerk_time *= 2.0f;
}