2015-08-11 03:28:41 -03:00
# include <AP_HAL/AP_HAL.h>
# include <AP_Math/AP_Math.h>
2020-04-08 03:20:56 -03:00
# include <AP_Terrain/AP_Terrain.h>
# include "AC_Circle.h"
2014-01-27 01:08:11 -04:00
2022-03-03 23:29:46 -04:00
# include <AP_Logger/AP_Logger.h>
2014-01-27 01:08:11 -04:00
extern const AP_HAL : : HAL & hal ;
2015-10-25 14:03:46 -03:00
const AP_Param : : GroupInfo AC_Circle : : var_info [ ] = {
2014-01-27 01:08:11 -04:00
// @Param: RADIUS
// @DisplayName: Circle Radius
// @Description: Defines the radius of the circle the vehicle will fly when in Circle flight mode
// @Units: cm
2020-01-06 18:35:17 -04:00
// @Range: 0 200000
2014-01-27 01:08:11 -04:00
// @Increment: 100
// @User: Standard
2021-04-09 03:33:28 -03:00
AP_GROUPINFO ( " RADIUS " , 0 , AC_Circle , _radius_parm , AC_CIRCLE_RADIUS_DEFAULT ) ,
2014-01-27 01:08:11 -04:00
// @Param: RATE
// @DisplayName: Circle rate
2023-01-23 15:20:30 -04:00
// @Description: Circle mode's turn rate in deg/sec. Positive to turn clockwise, negative for counter clockwise. Circle rate must be less than ATC_SLEW_YAW parameter.
2014-01-27 01:08:11 -04:00
// @Units: deg/s
// @Range: -90 90
// @Increment: 1
// @User: Standard
2023-02-14 18:17:06 -04:00
AP_GROUPINFO ( " RATE " , 1 , AC_Circle , _rate_parm , AC_CIRCLE_RATE_DEFAULT ) ,
2014-01-27 01:08:11 -04:00
2020-09-15 11:50:33 -03:00
// @Param: OPTIONS
// @DisplayName: Circle options
// @Description: 0:Enable or disable using the pitch/roll stick control circle mode's radius and rate
2023-06-30 20:36:27 -03:00
// @Bitmask: 0:manual control, 1:face direction of travel, 2:Start at center rather than on perimeter, 3:Make Mount ROI the center of the circle
2020-01-06 18:35:17 -04:00
// @User: Standard
2020-09-15 11:50:33 -03:00
AP_GROUPINFO ( " OPTIONS " , 2 , AC_Circle , _options , 1 ) ,
2020-01-06 18:35:17 -04:00
2014-01-27 01:08:11 -04:00
AP_GROUPEND
} ;
// Default constructor.
// Note that the Vector/Matrix constructors already implicitly zero
// their values.
//
2017-02-11 18:33:43 -04:00
AC_Circle : : AC_Circle ( const AP_InertialNav & inav , const AP_AHRS_View & ahrs , AC_PosControl & pos_control ) :
2014-01-27 01:08:11 -04:00
_inav ( inav ) ,
_ahrs ( ahrs ) ,
2024-05-20 19:49:29 -03:00
_pos_control ( pos_control )
2014-01-27 01:08:11 -04:00
{
AP_Param : : setup_object_defaults ( this , var_info ) ;
2015-06-08 01:29:46 -03:00
// init flags
_flags . panorama = false ;
2023-02-14 18:17:06 -04:00
_rate = _rate_parm ;
2014-01-27 01:08:11 -04:00
}
2014-04-16 04:19:41 -03:00
/// init - initialise circle controller setting center specifically
2023-02-14 18:17:06 -04:00
/// set terrain_alt to true if center.z should be interpreted as an alt-above-terrain. Rate should be +ve in deg/sec for cw turn
2014-05-07 03:05:03 -03:00
/// caller should set the position controller's x,y and z speeds and accelerations before calling this
2023-02-14 18:17:06 -04:00
void AC_Circle : : init ( const Vector3p & center , bool terrain_alt , float rate_deg_per_sec )
2014-01-27 01:08:11 -04:00
{
2014-04-16 04:19:41 -03:00
_center = center ;
2020-04-08 03:20:56 -03:00
_terrain_alt = terrain_alt ;
2023-02-14 18:17:06 -04:00
_rate = rate_deg_per_sec ;
2014-05-07 03:05:03 -03:00
// initialise position controller (sets target roll angle, pitch angle and I terms based on vehicle current lean angles)
2021-05-03 22:40:32 -03:00
_pos_control . init_xy_controller_stopping_point ( ) ;
_pos_control . init_z_controller_stopping_point ( ) ;
2014-01-27 01:08:11 -04:00
// calculate velocities
2015-07-22 09:40:20 -03:00
calc_velocities ( true ) ;
2014-04-16 04:19:41 -03:00
// set start angle from position
init_start_angle ( false ) ;
2014-01-27 01:08:11 -04:00
}
2014-04-16 04:19:41 -03:00
/// init - initialise circle controller setting center using stopping point and projecting out based on the copter's heading
2014-05-07 03:05:03 -03:00
/// caller should set the position controller's x,y and z speeds and accelerations before calling this
2014-04-16 04:19:41 -03:00
void AC_Circle : : init ( )
2021-05-01 21:41:30 -03:00
{
2023-02-14 18:17:06 -04:00
// initialize radius and rate from params
2021-05-01 21:41:30 -03:00
_radius = _radius_parm ;
_last_radius_param = _radius_parm ;
2023-02-14 18:17:06 -04:00
_rate = _rate_parm ;
2021-05-01 21:41:30 -03:00
2014-05-07 03:05:03 -03:00
// initialise position controller (sets target roll angle, pitch angle and I terms based on vehicle current lean angles)
2021-05-03 22:40:32 -03:00
_pos_control . init_xy_controller_stopping_point ( ) ;
_pos_control . init_z_controller_stopping_point ( ) ;
2014-05-07 03:05:03 -03:00
// get stopping point
2021-06-17 22:19:53 -03:00
const Vector3p & stopping_point = _pos_control . get_pos_target_cm ( ) ;
2014-01-27 01:08:11 -04:00
// set circle center to circle_radius ahead of stopping point
2020-09-15 11:50:33 -03:00
_center = stopping_point ;
if ( ( _options . get ( ) & CircleOptions : : INIT_AT_CENTER ) = = 0 ) {
_center . x + = _radius * _ahrs . cos_yaw ( ) ;
_center . y + = _radius * _ahrs . sin_yaw ( ) ;
}
2020-04-08 03:20:56 -03:00
_terrain_alt = false ;
2014-01-27 01:08:11 -04:00
// calculate velocities
2015-07-22 09:40:20 -03:00
calc_velocities ( true ) ;
2014-04-16 04:19:41 -03:00
// set starting angle from vehicle heading
init_start_angle ( true ) ;
2014-01-27 01:08:11 -04:00
}
2020-04-08 03:20:56 -03:00
/// set circle center to a Location
void AC_Circle : : set_center ( const Location & center )
{
if ( center . get_alt_frame ( ) = = Location : : AltFrame : : ABOVE_TERRAIN ) {
// convert Location with terrain altitude
Vector2f center_xy ;
int32_t terr_alt_cm ;
if ( center . get_vector_xy_from_origin_NE ( center_xy ) & & center . get_alt_cm ( Location : : AltFrame : : ABOVE_TERRAIN , terr_alt_cm ) ) {
set_center ( Vector3f ( center_xy . x , center_xy . y , terr_alt_cm ) , true ) ;
} else {
// failed to convert location so set to current position and log error
2021-11-22 22:10:53 -04:00
set_center ( _inav . get_position_neu_cm ( ) , false ) ;
2023-07-13 21:58:04 -03:00
LOGGER_WRITE_ERROR ( LogErrorSubsystem : : NAVIGATION , LogErrorCode : : FAILED_CIRCLE_INIT ) ;
2020-04-08 03:20:56 -03:00
}
} else {
// convert Location with alt-above-home, alt-above-origin or absolute alt
Vector3f circle_center_neu ;
if ( ! center . get_vector_from_origin_NEU ( circle_center_neu ) ) {
// default to current position and log error
2021-11-22 22:10:53 -04:00
circle_center_neu = _inav . get_position_neu_cm ( ) ;
2023-07-13 21:58:04 -03:00
LOGGER_WRITE_ERROR ( LogErrorSubsystem : : NAVIGATION , LogErrorCode : : FAILED_CIRCLE_INIT ) ;
2020-04-08 03:20:56 -03:00
}
set_center ( circle_center_neu , false ) ;
}
}
2015-07-22 09:40:20 -03:00
/// set_circle_rate - set circle rate in degrees per second
void AC_Circle : : set_rate ( float deg_per_sec )
{
2023-02-14 18:17:06 -04:00
if ( ! is_equal ( deg_per_sec , _rate ) ) {
_rate = deg_per_sec ;
2015-07-22 09:40:20 -03:00
}
}
2020-01-06 18:35:17 -04:00
/// set_circle_rate - set circle rate in degrees per second
2022-02-06 12:37:10 -04:00
void AC_Circle : : set_radius_cm ( float radius_cm )
2020-01-06 18:35:17 -04:00
{
_radius = constrain_float ( radius_cm , 0 , AC_CIRCLE_RADIUS_MAX ) ;
}
2020-04-21 08:42:32 -03:00
/// returns true if update has been run recently
/// used by vehicle code to determine if get_yaw() is valid
bool AC_Circle : : is_active ( ) const
{
return ( AP_HAL : : millis ( ) - _last_update_ms < 200 ) ;
}
2014-01-27 01:08:11 -04:00
/// update - update circle controller
2021-05-03 22:40:32 -03:00
bool AC_Circle : : update ( float climb_rate_cms )
2014-01-27 01:08:11 -04:00
{
2020-06-23 08:32:34 -03:00
calc_velocities ( false ) ;
2014-01-27 01:08:11 -04:00
// calculate dt
2021-05-19 11:10:32 -03:00
const float dt = _pos_control . get_dt ( ) ;
2014-01-27 01:08:11 -04:00
2018-03-08 22:40:56 -04:00
// ramp angular velocity to maximum
if ( _angular_vel < _angular_vel_max ) {
_angular_vel + = fabsf ( _angular_accel ) * dt ;
_angular_vel = MIN ( _angular_vel , _angular_vel_max ) ;
}
if ( _angular_vel > _angular_vel_max ) {
_angular_vel - = fabsf ( _angular_accel ) * dt ;
_angular_vel = MAX ( _angular_vel , _angular_vel_max ) ;
2014-01-27 01:08:11 -04:00
}
2018-03-08 22:40:56 -04:00
2023-10-11 04:41:50 -03:00
// update the target angle and total angle travelled
2018-03-08 22:40:56 -04:00
float angle_change = _angular_vel * dt ;
_angle + = angle_change ;
_angle = wrap_PI ( _angle ) ;
_angle_total + = angle_change ;
2020-04-08 03:20:56 -03:00
// calculate terrain adjustments
float terr_offset = 0.0f ;
if ( _terrain_alt & & ! get_terrain_offset ( terr_offset ) ) {
return false ;
}
2020-04-19 21:33:36 -03:00
// calculate z-axis target
float target_z_cm ;
if ( _terrain_alt ) {
target_z_cm = _center . z + terr_offset ;
} else {
2021-05-03 22:40:32 -03:00
target_z_cm = _pos_control . get_pos_target_z_cm ( ) ;
2020-04-19 21:33:36 -03:00
}
2018-03-08 22:40:56 -04:00
// if the circle_radius is zero we are doing panorama so no need to update loiter target
2021-06-17 22:19:53 -03:00
Vector3p target {
2021-05-19 11:10:32 -03:00
_center . x ,
_center . y ,
target_z_cm
} ;
2018-03-08 22:40:56 -04:00
if ( ! is_zero ( _radius ) ) {
// calculate target position
2021-05-19 11:10:32 -03:00
target . x + = _radius * cosf ( - _angle ) ;
target . y + = - _radius * sinf ( - _angle ) ;
2018-03-08 22:40:56 -04:00
2019-04-24 23:35:19 -03:00
// heading is from vehicle to center of circle
2021-11-22 22:10:53 -04:00
_yaw = get_bearing_cd ( _inav . get_position_xy_cm ( ) , _center . tofloat ( ) . xy ( ) ) ;
2020-09-15 11:50:33 -03:00
if ( ( _options . get ( ) & CircleOptions : : FACE_DIRECTION_OF_TRAVEL ) ! = 0 ) {
_yaw + = is_positive ( _rate ) ? - 9000.0f : 9000.0f ;
_yaw = wrap_360_cd ( _yaw ) ;
}
2018-03-08 22:40:56 -04:00
} else {
// heading is same as _angle but converted to centi-degrees
_yaw = _angle * DEGX100 ;
}
2021-05-03 22:40:32 -03:00
// update position controller target
2021-06-21 04:26:40 -03:00
Vector2f zero ;
_pos_control . input_pos_vel_accel_xy ( target . xy ( ) , zero , zero ) ;
if ( _terrain_alt ) {
float zero2 = 0 ;
2021-06-17 22:19:53 -03:00
float target_zf = target . z ;
_pos_control . input_pos_vel_accel_z ( target_zf , zero2 , 0 ) ;
2021-05-03 22:40:32 -03:00
} else {
2021-08-31 01:18:40 -03:00
_pos_control . set_pos_target_z_from_climb_rate_cm ( climb_rate_cms ) ;
2021-05-03 22:40:32 -03:00
}
2018-03-08 22:40:56 -04:00
// update position controller
2018-09-05 06:44:08 -03:00
_pos_control . update_xy_controller ( ) ;
2020-04-08 03:20:56 -03:00
2020-04-21 08:42:32 -03:00
// set update time
_last_update_ms = AP_HAL : : millis ( ) ;
2020-04-08 03:20:56 -03:00
return true ;
2014-01-27 01:08:11 -04:00
}
2014-04-16 04:19:41 -03:00
// get_closest_point_on_circle - returns closest point on the circle
// circle's center should already have been set
// closest point on the circle will be placed in result
// result's altitude (i.e. z) will be set to the circle_center's altitude
// if vehicle is at the center of the circle, the edge directly behind vehicle will be returned
2020-01-06 18:35:17 -04:00
void AC_Circle : : get_closest_point_on_circle ( Vector3f & result ) const
2014-04-16 04:19:41 -03:00
{
// return center if radius is zero
if ( _radius < = 0 ) {
2021-06-17 22:19:53 -03:00
result = _center . tofloat ( ) ;
2014-04-16 04:19:41 -03:00
return ;
}
// get current position
2021-06-17 22:19:53 -03:00
Vector2p stopping_point ;
2021-05-03 22:40:32 -03:00
_pos_control . get_stopping_point_xy_cm ( stopping_point ) ;
2014-04-16 04:19:41 -03:00
2018-07-20 03:08:41 -03:00
// calc vector from stopping point to circle center
2021-06-17 22:19:53 -03:00
Vector2f vec = ( stopping_point - _center . xy ( ) ) . tofloat ( ) ;
2021-06-21 04:26:40 -03:00
float dist = vec . length ( ) ;
2014-04-16 04:19:41 -03:00
// if current location is exactly at the center of the circle return edge directly behind vehicle
2015-05-04 23:34:54 -03:00
if ( is_zero ( dist ) ) {
2014-04-16 04:19:41 -03:00
result . x = _center . x - _radius * _ahrs . cos_yaw ( ) ;
result . y = _center . y - _radius * _ahrs . sin_yaw ( ) ;
result . z = _center . z ;
return ;
}
// calculate closest point on edge of circle
result . x = _center . x + vec . x / dist * _radius ;
result . y = _center . y + vec . y / dist * _radius ;
result . z = _center . z ;
}
2014-01-27 01:08:11 -04:00
// calc_velocities - calculate angular velocity max and acceleration based on radius and rate
// this should be called whenever the radius or rate are changed
// initialises the yaw and current position around the circle
2015-07-22 09:40:20 -03:00
void AC_Circle : : calc_velocities ( bool init_velocity )
2014-01-27 01:08:11 -04:00
{
// if we are doing a panorama set the circle_angle to the current heading
if ( _radius < = 0 ) {
_angular_vel_max = ToRad ( _rate ) ;
2015-11-27 13:11:58 -04:00
_angular_accel = MAX ( fabsf ( _angular_vel_max ) , ToRad ( AC_CIRCLE_ANGULAR_ACCEL_MIN ) ) ; // reach maximum yaw velocity in 1 second
2014-01-27 01:08:11 -04:00
} else {
// calculate max velocity based on waypoint speed ensuring we do not use more than half our max acceleration for accelerating towards the center of the circle
2021-05-03 22:40:32 -03:00
float velocity_max = MIN ( _pos_control . get_max_speed_xy_cms ( ) , safe_sqrt ( 0.5f * _pos_control . get_max_accel_xy_cmss ( ) * _radius ) ) ;
2014-01-27 01:08:11 -04:00
// angular_velocity in radians per second
_angular_vel_max = velocity_max / _radius ;
_angular_vel_max = constrain_float ( ToRad ( _rate ) , - _angular_vel_max , _angular_vel_max ) ;
// angular_velocity in radians per second
2021-05-03 22:40:32 -03:00
_angular_accel = MAX ( _pos_control . get_max_accel_xy_cmss ( ) / _radius , ToRad ( AC_CIRCLE_ANGULAR_ACCEL_MIN ) ) ;
2014-01-27 01:08:11 -04:00
}
2014-04-16 04:19:41 -03:00
// initialise angular velocity
2015-07-22 09:40:20 -03:00
if ( init_velocity ) {
_angular_vel = 0 ;
}
2014-01-27 01:08:11 -04:00
}
2014-04-16 04:19:41 -03:00
// init_start_angle - sets the starting angle around the circle and initialises the angle_total
// if use_heading is true the vehicle's heading will be used to init the angle causing minimum yaw movement
// if use_heading is false the vehicle's position from the center will be used to initialise the angle
void AC_Circle : : init_start_angle ( bool use_heading )
{
// initialise angle total
_angle_total = 0 ;
// if the radius is zero we are doing panorama so init angle to the current heading
if ( _radius < = 0 ) {
_angle = _ahrs . yaw ;
return ;
}
// if use_heading is true
if ( use_heading ) {
2016-02-25 13:13:02 -04:00
_angle = wrap_PI ( _ahrs . yaw - M_PI ) ;
2014-04-16 04:19:41 -03:00
} else {
// if we are exactly at the center of the circle, init angle to directly behind vehicle (so vehicle will backup but not change heading)
2021-11-22 22:10:53 -04:00
const Vector3f & curr_pos = _inav . get_position_neu_cm ( ) ;
2021-06-17 22:19:53 -03:00
if ( is_equal ( curr_pos . x , float ( _center . x ) ) & & is_equal ( curr_pos . y , float ( _center . y ) ) ) {
2016-02-25 13:13:02 -04:00
_angle = wrap_PI ( _ahrs . yaw - M_PI ) ;
2014-04-16 04:19:41 -03:00
} else {
// get bearing from circle center to vehicle in radians
2015-05-04 19:42:05 -03:00
float bearing_rad = atan2f ( curr_pos . y - _center . y , curr_pos . x - _center . x ) ;
2014-04-16 04:19:41 -03:00
_angle = wrap_PI ( bearing_rad ) ;
}
}
}
2020-04-08 03:20:56 -03:00
// get expected source of terrain data
AC_Circle : : TerrainSource AC_Circle : : get_terrain_source ( ) const
{
// use range finder if connected
2020-04-09 02:06:24 -03:00
if ( _rangefinder_available ) {
2020-04-08 03:20:56 -03:00
return AC_Circle : : TerrainSource : : TERRAIN_FROM_RANGEFINDER ;
}
# if AP_TERRAIN_AVAILABLE
const AP_Terrain * terrain = AP_Terrain : : get_singleton ( ) ;
if ( ( terrain ! = nullptr ) & & terrain - > enabled ( ) ) {
return AC_Circle : : TerrainSource : : TERRAIN_FROM_TERRAINDATABASE ;
} else {
return AC_Circle : : TerrainSource : : TERRAIN_UNAVAILABLE ;
}
# else
return AC_Circle : : TerrainSource : : TERRAIN_UNAVAILABLE ;
# endif
}
// 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_Circle : : get_terrain_offset ( float & offset_cm )
{
// calculate offset based on source (rangefinder or terrain database)
switch ( get_terrain_source ( ) ) {
case AC_Circle : : TerrainSource : : TERRAIN_UNAVAILABLE :
return false ;
case AC_Circle : : TerrainSource : : TERRAIN_FROM_RANGEFINDER :
if ( _rangefinder_healthy ) {
2023-03-03 21:01:13 -04:00
offset_cm = _rangefinder_terrain_offset_cm ;
2020-04-08 03:20:56 -03:00
return true ;
}
return false ;
case AC_Circle : : TerrainSource : : TERRAIN_FROM_TERRAINDATABASE :
# if AP_TERRAIN_AVAILABLE
float terr_alt = 0.0f ;
AP_Terrain * terrain = AP_Terrain : : get_singleton ( ) ;
if ( terrain ! = nullptr & & terrain - > height_above_terrain ( terr_alt , true ) ) {
2021-11-22 22:10:53 -04:00
offset_cm = _inav . get_position_z_up_cm ( ) - ( terr_alt * 100.0 ) ;
2020-04-08 03:20:56 -03:00
return true ;
}
# endif
return false ;
}
// we should never get here but just in case
return false ;
}
2021-05-01 21:41:30 -03:00
void AC_Circle : : check_param_change ( )
{
if ( ! is_equal ( _last_radius_param , _radius_parm . get ( ) ) ) {
_radius = _radius_parm ;
_last_radius_param = _radius_parm ;
}
}