LCOV - code coverage report
Current view: top level - AC_WPNav - AC_Circle.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 132 181 72.9 %
Date: 2026-09-09 11:02:08 Functions: 17 19 89.5 %

          Line data    Source code
       1             : #include <AP_HAL/AP_HAL.h>
       2             : #include <AP_Math/AP_Math.h>
       3             : #include <AP_Terrain/AP_Terrain.h>
       4             : #include "AC_Circle.h"
       5             : #include <AP_Logger/AP_Logger.h>
       6             : #include <AP_Vehicle/AP_Vehicle_Type.h>
       7             : 
       8             : extern const AP_HAL::HAL& hal;
       9             : 
      10             : // Circle mode default and limit constants
      11             : #define AC_CIRCLE_RADIUS_M_DEFAULT   10.0f      // Default circle radius in meters
      12             : #define AC_CIRCLE_RATE_DEFAULT       20.0f      // Default circle turn rate in degrees per second. Positive = clockwise, negative = counter-clockwise.
      13             : #define AC_CIRCLE_ANGULAR_ACCEL_MIN  2.0f       // Minimum angular acceleration in deg/s² (used to avoid sluggish yaw transitions).
      14             : #define AC_CIRCLE_RADIUS_MAX_M       2000.0     // Maximum allowed circle radius in meters (2000 m = 2 km).
      15             : 
      16             : const AP_Param::GroupInfo AC_Circle::var_info[] = {
      17             :     // 0 was RADIUS (cm)
      18             : 
      19             :     // @Param: RATE
      20             :     // @DisplayName: Circle rate
      21             :     // @Description: Circle mode's maximum turn rate in deg/sec.  Positive to turn clockwise, negative for counter clockwise. Final circle rate is also limited by speed and acceleration settings.
      22             :     // @Units: deg/s
      23             :     // @Range: -90 90
      24             :     // @Increment: 1
      25             :     // @User: Standard
      26             :     AP_GROUPINFO("RATE",    1, AC_Circle, _rate_parm_degs,    AC_CIRCLE_RATE_DEFAULT),
      27             : 
      28             :     // @Param: OPTIONS
      29             :     // @DisplayName: Circle options
      30             :     // @Description: Circle behaviour options
      31             :     // @Bitmask: 0:RC pitch and roll control radius and rate, 1:Face direction of travel, 2:Start at center rather than on perimeter, 3:Make Mount ROI the center of the circle
      32             :     // @User: Standard
      33             :     AP_GROUPINFO("OPTIONS", 2, AC_Circle, _options, 1),
      34             : 
      35             :     // @Param: RADIUS_M
      36             :     // @DisplayName: Circle Radius
      37             :     // @Description: Radius of the circle the vehicle will fly when in Circle flight mode
      38             :     // @Units: m
      39             :     // @Range: 0 2000
      40             :     // @Increment: 1
      41             :     // @User: Standard
      42             :     AP_GROUPINFO("RADIUS_M", 3, AC_Circle, _radius_parm_m, AC_CIRCLE_RADIUS_M_DEFAULT),
      43             : 
      44             :     AP_GROUPEND
      45             : };
      46             : 
      47             : // Default constructor.
      48             : // Note that the Vector/Matrix constructors already implicitly zero
      49             : // their values.
      50        1084 : AC_Circle::AC_Circle(const AP_AHRS_View& ahrs, AC_PosControl& pos_control) :
      51        1084 :     _ahrs(ahrs),
      52        1084 :     _pos_control(pos_control)
      53             : {
      54        1084 :     AP_Param::setup_object_defaults(this, var_info);
      55             : 
      56        1084 :     _rotation_rate_max_rads = radians(_rate_parm_degs);
      57        1084 : }
      58             : 
      59             : // Initializes circle flight using a center position in centimeters relative to the EKF origin.
      60             : // See init_NED_m() for full details.
      61           2 : void AC_Circle::init_NEU_cm(const Vector3p& center_neu_cm, bool is_terrain_alt, float rate_degs)
      62             : {
      63             :     // Convert input from NEU cm to NED meters and delegate to meter-based initializer
      64           2 :     Vector3p center_ned_m = Vector3p{center_neu_cm.x, center_neu_cm.y, -center_neu_cm.z} * 0.01;
      65           2 :     init_NED_m(center_ned_m, is_terrain_alt, rate_degs);
      66           2 : }
      67             : 
      68             : // Initializes circle flight mode using a specified center position in meters.
      69             : // Parameters:
      70             : //  - center_ned_m: Center of the circle in NED frame (meters, relative to EKF origin)
      71             : //  - is_terrain_alt: If true, center_ned_m.z is interpreted as relative to terrain; otherwise, above EKF origin
      72             : //  - rate_degs: Desired turn rate in degrees per second (positive = clockwise, negative = counter-clockwise)
      73             : // Caller must preconfigure the position controller's speed and acceleration settings before calling.
      74           2 : void AC_Circle::init_NED_m(const Vector3p& center_ned_m, bool is_terrain_alt, float rate_degs)
      75             : {
      76             :     // Store circle center and frame reference
      77           2 :     _center_ned_m = center_ned_m;
      78           2 :     _is_terrain_alt = is_terrain_alt;
      79             : 
      80             :     // Convert desired turn rate from degrees to radians
      81           2 :     _rotation_rate_max_rads = radians(rate_degs);
      82             : 
      83             :     // Initialise position controller using current lean angles
      84           2 :     _pos_control.NE_init_controller_stopping_point();
      85           2 :     _pos_control.D_init_controller_stopping_point();
      86             : 
      87             :     // Calculate velocity and acceleration limits based on circle configuration
      88           2 :     calc_velocities(true);
      89             : 
      90             :     // Use position-based angle initialization (avoids sharp yaw discontinuity)
      91           2 :     init_start_angle(false);
      92           2 : }
      93             : 
      94             : // Initializes the circle flight mode using the current stopping point as a reference.
      95             : // If the INIT_AT_CENTER option is not set, the circle center is projected one radius ahead along the vehicle's heading.
      96             : // Caller must configure position controller speeds and accelerations beforehand.
      97        2270 : void AC_Circle::init()
      98             : {
      99             :     // Load radius and rate from parameters
     100        2270 :     _radius_m = _radius_parm_m.get();
     101        2270 :     _last_radius_param_m = _radius_m;
     102        2270 :     _rotation_rate_max_rads = radians(_rate_parm_degs);
     103             : 
     104             :     // Initialise position controller using current lean angles
     105        2270 :     _pos_control.NE_init_controller_stopping_point();
     106        2270 :     _pos_control.D_init_controller_stopping_point();
     107             : 
     108             :     // Get stopping point as initial reference for center
     109        2270 :     const Vector3p& stopping_point_ned_m = _pos_control.get_pos_desired_NED_m();
     110             : 
     111             :     // By default, set center to stopping point
     112        2270 :     _center_ned_m = stopping_point_ned_m;
     113             : 
     114             :     // If INIT_AT_CENTER is not set, project center forward by radius in heading direction
     115        2270 :     if ((_options.get() & CircleOptions::INIT_AT_CENTER) == 0) {
     116        2270 :         _center_ned_m.x += _radius_m * _ahrs.cos_yaw();
     117        2270 :         _center_ned_m.y += _radius_m * _ahrs.sin_yaw();
     118             :     }
     119             : 
     120             :     // Circle altitude is relative to EKF origin by default
     121        2270 :     _is_terrain_alt = false;
     122             : 
     123             :     // Calculate velocity and acceleration constraints
     124        2270 :     calc_velocities(true);
     125             : 
     126             :     // Initialize angle using vehicle heading
     127        2270 :     init_start_angle(true);
     128        2270 : }
     129             : 
     130             : // Sets the circle center using a Location object.
     131             : // Automatically determines whether the location uses terrain-relative or origin-relative altitude.
     132             : // If conversion fails, defaults to current position and logs a navigation error.
     133           7 : void AC_Circle::set_center(const Location& center)
     134             : {
     135             :     // Check if the location uses terrain-relative altitude
     136           7 :     if (center.get_alt_frame() == Location::AltFrame::ABOVE_TERRAIN) {
     137             :         // convert Location with terrain altitude
     138           0 :         Vector2p center_ne_m;
     139             :         float terr_alt_m;
     140             : 
     141             :         // Attempt to convert XY and Z to NEU frame with terrain altitude
     142           0 :         if (center.get_vector_xy_from_origin_NE_m(center_ne_m) && center.get_alt_m(Location::AltFrame::ABOVE_TERRAIN, terr_alt_m)) {
     143           0 :             set_center_NED_m(Vector3p{center_ne_m.x, center_ne_m.y, -terr_alt_m}, true);
     144             :         } else {
     145             :             // Conversion failed: fall back to current position and log error
     146           0 :             set_center_NED_m(_pos_control.get_pos_estimate_NED_m(), false);
     147           0 :             LOGGER_WRITE_ERROR(LogErrorSubsystem::NAVIGATION, LogErrorCode::FAILED_CIRCLE_INIT);
     148             :         }
     149             :     } else {
     150             :         // Handle alt-above-origin, alt-above-home, or absolute altitudes
     151           7 :         Vector3p circle_center_ned_m;
     152           7 :         if (!center.get_vector_from_origin_NED_m(circle_center_ned_m)) {
     153             :             // Conversion failed: fall back to current position and log error
     154           0 :             circle_center_ned_m = _pos_control.get_pos_estimate_NED_m();
     155           0 :             LOGGER_WRITE_ERROR(LogErrorSubsystem::NAVIGATION, LogErrorCode::FAILED_CIRCLE_INIT);
     156             :         }
     157             : 
     158             :         // Apply converted center and mark it as origin-relative
     159           7 :         set_center_NED_m(circle_center_ned_m, false);
     160             :     }
     161           7 : }
     162             : 
     163             : // Sets the target circle rate in degrees per second.
     164             : // Positive values result in clockwise rotation; negative for counter-clockwise.
     165       16175 : void AC_Circle::set_rate_degs(float rate_degs)
     166             : {
     167             :     // Convert the rate from degrees/sec to radians/sec
     168       16175 :     _rotation_rate_max_rads = radians(rate_degs);
     169       16175 : }
     170             : 
     171             : // Sets the circle radius in centimeters.
     172             : // See set_radius_m() for full details.
     173           2 : void AC_Circle::set_radius_cm(float radius_cm)
     174             : {
     175             :     // Convert input from cm to meters and call set_radius_m()
     176           2 :     set_radius_m(radius_cm * 0.01);
     177           2 : }
     178             : 
     179             : // Sets the circle radius in meters.
     180             : // Radius is constrained to AC_CIRCLE_RADIUS_MAX_M.
     181        6415 : void AC_Circle::set_radius_m(float radius_m)
     182             : {
     183             :     // Constrain the radius to prevent unsafe or invalid circle sizes
     184        6415 :     _radius_m = constrain_float(radius_m, 0, AC_CIRCLE_RADIUS_MAX_M);
     185        6415 : }
     186             : 
     187             : // Returns true if the circle controller's update() function has run recently.
     188             : // Used by vehicle code to determine if yaw and position outputs are valid.
     189      156551 : bool AC_Circle::is_active() const
     190             : {
     191             :     // Consider the controller active if the last update occurred within the past 200 milliseconds
     192      156551 :     return (AP_HAL::millis() - _last_update_ms < 200);
     193             : }
     194             : 
     195             : // Updates the circle controller using a climb rate in cm/s.
     196             : // See update_ms() for full implementation details.
     197      127956 : bool AC_Circle::update_cms(float climb_rate_cms)
     198             : {
     199             :     // Convert climb rate from cm/s to m/s and call the meter-based update
     200      127956 :     return update_ms(climb_rate_cms * 0.01);
     201             : }
     202             : 
     203             : // Updates the circle controller using a climb rate in m/s.
     204             : // Computes new angular position, yaw, and vertical trajectory, then updates the position controller.
     205             : // Returns false if terrain data is required but unavailable.
     206      237837 : bool AC_Circle::update_ms(float climb_rate_ms)
     207             : {
     208             :     // Recalculate angular velocities based on the current radius and rate
     209      237837 :     calc_velocities(false);
     210             : 
     211             :     // calculate dt
     212      237837 :     const float dt = _pos_control.get_dt_s();
     213             : 
     214             :     // ramp angular velocity to maximum
     215      237837 :     if (_angular_vel_rads < _angular_vel_max_rads) {
     216        8296 :         _angular_vel_rads += fabsf(_angular_accel_radss) * dt;
     217        8296 :         _angular_vel_rads = MIN(_angular_vel_rads, _angular_vel_max_rads);
     218             :     }
     219      237837 :     if (_angular_vel_rads > _angular_vel_max_rads) {
     220       12424 :         _angular_vel_rads -= fabsf(_angular_accel_radss) * dt;
     221       12424 :         _angular_vel_rads = MAX(_angular_vel_rads, _angular_vel_max_rads);
     222             :     }
     223             : 
     224             :     // update the target angle and total angle travelled
     225      237837 :     float angle_change_rad = _angular_vel_rads * dt;
     226      237837 :     _angle_rad += angle_change_rad;
     227      237837 :     _angle_rad = wrap_PI(_angle_rad);
     228      237837 :     _angle_total_rad += angle_change_rad;
     229             : 
     230             :     // calculate terrain adjustments
     231      237837 :     float terrain_u_m = 0.0f;
     232      237837 :     if (_is_terrain_alt && !get_terrain_U_m(terrain_u_m)) {
     233           0 :         return false;
     234             :     }
     235             : 
     236             :     // calculate z-axis target
     237             :     float target_d_m;
     238      237837 :     if (_is_terrain_alt) {
     239           0 :         target_d_m = _center_ned_m.z - terrain_u_m;
     240             :     } else {
     241      237837 :         target_d_m = -_pos_control.get_pos_desired_U_m();
     242             :     }
     243             : 
     244             :     // Construct target position centered on the circle center
     245             :     Vector3p target_ned_m {
     246             :         _center_ned_m.x,
     247             :         _center_ned_m.y,
     248             :         target_d_m
     249      237837 :     };
     250      237837 :     if (!is_zero(_radius_m)) {
     251             :         // Calculate position on the circle edge based on current angle
     252      237837 :         target_ned_m.x += _radius_m * cosf(-_angle_rad);
     253      237837 :         target_ned_m.y += - _radius_m * sinf(-_angle_rad);
     254             : 
     255             :         // Compute yaw toward the circle center
     256      237837 :         _yaw_rad = get_bearing_rad(_pos_control.get_pos_desired_NED_m().xy().tofloat(), _center_ned_m.xy().tofloat());
     257             : 
     258             :         // Optionally adjust yaw to face direction of travel
     259      237837 :         if ((_options.get() & CircleOptions::FACE_DIRECTION_OF_TRAVEL) != 0) {
     260           0 :             _yaw_rad += is_positive(_rotation_rate_max_rads) ? -radians(90.0) : radians(90.0);
     261           0 :             _yaw_rad = wrap_2PI(_yaw_rad);
     262             :         }
     263             :     } else {
     264             :         // set heading be the same as the angle for zero radius
     265           0 :         _yaw_rad = _angle_rad;
     266             :     }
     267             : 
     268             :     // update position controller target
     269      237837 :     Vector2f zero_ne;
     270      237837 :     _pos_control.input_pos_vel_accel_NE_m(target_ned_m.xy(), zero_ne, zero_ne);
     271      237837 :     if (_is_terrain_alt) {
     272           0 :         float vel_zero = 0;
     273           0 :         float target_pos_d_m = target_ned_m.z;
     274           0 :         _pos_control.input_pos_vel_accel_D_m(target_pos_d_m, vel_zero, 0);
     275             :     } else {
     276      237837 :         _pos_control.D_set_pos_target_from_climb_rate_ms(climb_rate_ms);
     277             :     }
     278             : 
     279             :     // update position controller
     280      237837 :     _pos_control.NE_update_controller();
     281             : 
     282             :     // set update time
     283      237837 :     _last_update_ms = AP_HAL::millis();
     284             : 
     285      237837 :     return true;
     286             : }
     287             : 
     288             : // Returns the closest point on the circle to the vehicle's current position in centimeters.
     289             : // See get_closest_point_on_circle_NED_m() for full details.
     290           2 : void AC_Circle::get_closest_point_on_circle_NEU_cm(Vector3f& result_neu_cm, float& dist_cm) const
     291             : {
     292           2 :     Vector3p result_ned_m;
     293             :     float dist_m;
     294             : 
     295             :     // Compute closest point in meters
     296           2 :     get_closest_point_on_circle_NED_m(result_ned_m, dist_m);
     297             : 
     298             :     // Convert results back to neu centimeters
     299           2 :     result_neu_cm = Vector3f(result_ned_m.x, result_ned_m.y, -result_ned_m.z) * 100.0;
     300           2 :     dist_cm = dist_m * 100.0;
     301           2 : }
     302             : 
     303             : // Returns the closest point on the circle to the vehicle's stopping point in meters.
     304             : // The result vector is updated with the NEU position of the closest point on the circle.
     305             : // The altitude (z) is set to match the circle center's altitude.
     306             : // dist_m is updated with the 3D distance to the circle edge from the stopping point.
     307             : // If the vehicle is at the center, the point directly behind the vehicle (based on yaw) is returned.
     308           7 : void AC_Circle::get_closest_point_on_circle_NED_m(Vector3p& result_ned_m, float& dist_to_edge_m) const
     309             : {
     310             :     // Get vehicle stopping point from the position controller (in NEU frame, meters)
     311           7 :     Vector3p stopping_point_ned_m;
     312           7 :     _pos_control.get_stopping_point_NE_m(stopping_point_ned_m.xy());
     313           7 :     _pos_control.get_stopping_point_D_m(stopping_point_ned_m.z);
     314             : 
     315             :     // Compute vector from stopping point to the circle center
     316           7 :     Vector3f vec_from_center_ned_m = (stopping_point_ned_m - _center_ned_m).tofloat();
     317             :     // Return circle center if radius is zero (vehicle orbits in place)
     318           7 :     if (!is_positive(_radius_m)) {
     319           1 :         result_ned_m = _center_ned_m;
     320           1 :         dist_to_edge_m = 0;
     321           1 :         return;
     322             :     }
     323             : 
     324           6 :     const float dist_to_center_m_sq = vec_from_center_ned_m.length_squared();
     325             :     // Handle edge case: vehicle is at the exact center of the circle
     326           6 :     if (dist_to_center_m_sq < sq(0.5)) {
     327           0 :         result_ned_m.x = _center_ned_m.x - _radius_m * _ahrs.cos_yaw();
     328           0 :         result_ned_m.y = _center_ned_m.y - _radius_m * _ahrs.sin_yaw();
     329           0 :         result_ned_m.z = _center_ned_m.z;
     330           0 :         dist_to_edge_m = (stopping_point_ned_m - result_ned_m).length();
     331           0 :         return;
     332             :     }
     333             : 
     334             :     // Calculate the closest point on the circle's edge by projecting out from center
     335           6 :     const float dist_to_center_m_xy = vec_from_center_ned_m.xy().length();
     336           6 :     result_ned_m.x = _center_ned_m.x + vec_from_center_ned_m.x / dist_to_center_m_xy * _radius_m;
     337           6 :     result_ned_m.y = _center_ned_m.y + vec_from_center_ned_m.y / dist_to_center_m_xy * _radius_m;
     338           6 :     result_ned_m.z = _center_ned_m.z;
     339           6 :     dist_to_edge_m = (stopping_point_ned_m - result_ned_m).length();
     340             : }
     341             : 
     342             : // Calculates angular velocity and acceleration limits based on the configured radius and rate.
     343             : // Should be called whenever radius or rate changes.
     344             : // If `init_velocity` is true, resets angular velocity to zero (used on controller startup).
     345      240109 : void AC_Circle::calc_velocities(bool init_velocity)
     346             : {
     347             :     // If performing a panorama (radius is zero), use current yaw rate and enforce minimum acceleration
     348      240109 :     if (_radius_m <= 0) {
     349           0 :         _angular_vel_max_rads = _rotation_rate_max_rads;
     350           0 :         _angular_accel_radss = MAX(fabsf(_angular_vel_max_rads), radians(AC_CIRCLE_ANGULAR_ACCEL_MIN));  // reach maximum yaw velocity in 1 second
     351             :     }else{
     352             :         // Limit max horizontal speed based on radius and available acceleration
     353      240109 :         float vel_max_ms = MIN(_pos_control.NE_get_max_speed_ms(), safe_sqrt(0.5f*_pos_control.NE_get_max_accel_mss()*_radius_m));
     354             : 
     355             :         // Convert linear speed to angular velocity (rad/s)
     356      240109 :         _angular_vel_max_rads = vel_max_ms/_radius_m;
     357             : 
     358             :         // Constrain to requested circle rate
     359      240109 :         _angular_vel_max_rads = constrain_float(_rotation_rate_max_rads, -_angular_vel_max_rads, _angular_vel_max_rads);
     360             : 
     361             :         // Derive maximum angular acceleration
     362      240109 :         _angular_accel_radss = MAX(_pos_control.NE_get_max_accel_mss() / _radius_m, radians(AC_CIRCLE_ANGULAR_ACCEL_MIN));
     363             :     }
     364             : 
     365             :     // Reset angular velocity to zero at initialization if requested
     366      240109 :     if (init_velocity) {
     367        2272 :         _angular_vel_rads = 0.0;
     368             :     }
     369      240109 : }
     370             : 
     371             : // Sets the initial angle around the circle and resets the accumulated angle.
     372             : // If `use_heading` is true, uses vehicle heading to initialize angle for minimal yaw motion.
     373             : // If false, uses position relative to circle center to set angle.
     374        2272 : void AC_Circle::init_start_angle(bool use_heading)
     375             : {
     376             :     // Reset the accumulated angle traveled around the circle
     377        2272 :     _angle_total_rad = 0.0;
     378             : 
     379             :     // If radius is zero, we're doing a panorama—set angle to current yaw heading
     380        2272 :     if (_radius_m <= 0) {
     381           0 :         _angle_rad = _ahrs.yaw;
     382           0 :         return;
     383             :     }
     384             : 
     385             :     // if use_heading is true
     386        2272 :     if (use_heading) {
     387             :         // Initialize angle directly behind current heading to minimize yaw motion
     388        2270 :         _angle_rad = wrap_PI(_ahrs.yaw - M_PI);
     389             :     } else {
     390             :         // If vehicle is exactly at the center, init angle behind vehicle (prevent undefined bearing)
     391           2 :         const Vector3p &curr_pos_desired_ned_m = _pos_control.get_pos_desired_NED_m();
     392           2 :         if (is_equal(curr_pos_desired_ned_m.x, _center_ned_m.x) && is_equal(curr_pos_desired_ned_m.y, _center_ned_m.y)) {
     393           0 :             _angle_rad = wrap_PI(_ahrs.yaw - M_PI);
     394             :         } else {
     395             :             // Calculate bearing from circle center to current position
     396           2 :             float bearing_rad = atan2f(curr_pos_desired_ned_m.y - _center_ned_m.y, curr_pos_desired_ned_m.x - _center_ned_m.x);
     397           2 :             _angle_rad = wrap_PI(bearing_rad);
     398             :         }
     399             :     }
     400             : }
     401             : 
     402             : // Returns the expected source of terrain data for the circle controller.
     403             : // Used to determine whether terrain offset comes from rangefinder, terrain database, or is unavailable.
     404           0 : AC_Circle::TerrainSource AC_Circle::get_terrain_source() const
     405             : {
     406             :     // Use rangefinder if available and enabled
     407           0 :     if (_rangefinder_available) {
     408           0 :         return AC_Circle::TerrainSource::TERRAIN_FROM_RANGEFINDER;
     409             :     }
     410             : #if AP_TERRAIN_AVAILABLE
     411             :     // Fallback to terrain database if available and enabled
     412           0 :     const AP_Terrain *terrain = AP_Terrain::get_singleton();
     413           0 :     if ((terrain != nullptr) && terrain->enabled()) {
     414           0 :         return AC_Circle::TerrainSource::TERRAIN_FROM_TERRAINDATABASE;
     415             :     } else {
     416           0 :         return AC_Circle::TerrainSource::TERRAIN_UNAVAILABLE;
     417             :     }
     418             : #else
     419             :     // Terrain unavailable if no valid source
     420             :     return AC_Circle::TerrainSource::TERRAIN_UNAVAILABLE;
     421             : #endif
     422             : }
     423             : 
     424             : // Returns terrain offset in meters above the EKF origin at the current position.
     425             : // Positive values indicate terrain is above the EKF origin altitude.
     426             : // Terrain source may be rangefinder or terrain database.
     427           0 : bool AC_Circle::get_terrain_U_m(float& terrain_u_m)
     428             : {
     429             :     // Determine terrain source and calculate offset accordingly
     430           0 :     switch (get_terrain_source()) {
     431           0 :     case AC_Circle::TerrainSource::TERRAIN_UNAVAILABLE:
     432           0 :         return false;
     433           0 :     case AC_Circle::TerrainSource::TERRAIN_FROM_RANGEFINDER:
     434           0 :         if (_rangefinder_healthy) {
     435             :             // Use last known healthy rangefinder terrain offset
     436           0 :             terrain_u_m = _rangefinder_terrain_u_m;
     437           0 :             return true;
     438             :         }
     439           0 :         return false;
     440           0 :     case AC_Circle::TerrainSource::TERRAIN_FROM_TERRAINDATABASE:
     441             : #if AP_TERRAIN_AVAILABLE
     442           0 :         float terr_alt_m = 0.0f;
     443           0 :         AP_Terrain *terrain = AP_Terrain::get_singleton();
     444           0 :         if (terrain != nullptr && terrain->height_above_terrain(terr_alt_m, true)) {
     445             :             // Calculate offset from EKF origin altitude to terrain altitude
     446           0 :             terrain_u_m = _pos_control.get_pos_estimate_U_m() - terr_alt_m;
     447           0 :             return true;
     448             :         }
     449             : #endif
     450           0 :         return false;
     451             :     }
     452             : 
     453             :     // This fallback should never be reached
     454           0 :     return false;
     455             : }
     456             : 
     457             : // Checks if the circle radius parameter has changed.
     458             : // If so, updates internal `_radius_m` and stores the new parameter value.
     459      118591 : void AC_Circle::check_param_change()
     460             : {
     461             :     // Check if the stored radius param has changed
     462      118591 :     if (!is_equal(_last_radius_param_m, _radius_parm_m.get())) {
     463             :         // update internal radius and store new parameter value to detect future changes
     464           1 :         _radius_m = _radius_parm_m.get();
     465           1 :         _last_radius_param_m = _radius_m;
     466             :     }
     467      118591 : }
     468             : 
     469             : // perform any required parameter conversions
     470        1084 : void AC_Circle::convert_parameters()
     471             : {
     472             :     // PARAMETER_CONVERSION - Added: Jan-2026 for ArduPilot-4.7
     473             : 
     474             :     // exit immediately if radius_m parameter is already configured
     475        1084 :     if (_radius_parm_m.configured()) {
     476          32 :         return;
     477             :     }
     478             : 
     479             :     // convert parameters
     480             :     static const AP_Param::ConversionInfo conversion_info[] = {
     481             : #if APM_BUILD_TYPE(APM_BUILD_ArduSub)
     482             :         { 60, 0, AP_PARAM_FLOAT, "CIRCLE_RADIUS_M" },    // CIRCLE_RADIUS moved to CIRCLE_RADIUS_M
     483             : #else
     484             :         { 104, 0, AP_PARAM_FLOAT, "CIRCLE_RADIUS_M" },   // CIRCLE_RADIUS moved to CIRCLE_RADIUS_M
     485             : #endif
     486             :     };
     487        1052 :     AP_Param::convert_old_parameters_scaled(conversion_info, ARRAY_SIZE(conversion_info), 0.01, 0);
     488             : }

Generated by: LCOV version 1.14