LCOV - code coverage report
Current view: top level - AP_Follow - AP_Follow.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 265 344 77.0 %
Date: 2026-09-09 11:02:08 Functions: 21 25 84.0 %

          Line data    Source code
       1             : /*
       2             :     This program is free software: you can redistribute it and/or modify
       3             :     it under the terms of the GNU General Public License as published by
       4             :     the Free Software Foundation, either version 3 of the License, or
       5             :     (at your option) any later version.
       6             : 
       7             :     This program is distributed in the hope that it will be useful,
       8             :     but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :     GNU General Public License for more details.
      11             : 
      12             :     You should have received a copy of the GNU General Public License
      13             :     along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             : */
      15             : 
      16             : //==============================================================================
      17             : // AP_Follow Library
      18             : // Target Following Logic for ArduPilot Vehicles
      19             : //==============================================================================
      20             : 
      21             : 
      22             : //==============================================================================
      23             : // Includes
      24             : //==============================================================================
      25             : 
      26             : #include "AP_Follow_config.h"
      27             : 
      28             : #if AP_FOLLOW_ENABLED
      29             : 
      30             : #include <AP_HAL/AP_HAL.h>
      31             : #include "AP_Follow.h"
      32             : #include <ctype.h>
      33             : #include <stdio.h>
      34             : 
      35             : #include <AP_AHRS/AP_AHRS.h>
      36             : #include <AP_Logger/AP_Logger.h>
      37             : #include <GCS_MAVLink/GCS.h>
      38             : #include <AP_Vehicle/AP_Vehicle_Type.h>
      39             : #include <AP_Scheduler/AP_Scheduler.h>
      40             : 
      41             : extern const AP_HAL::HAL& hal;
      42             : 
      43             : 
      44             : //==============================================================================
      45             : // Constants and Definitions
      46             : //==============================================================================
      47             : 
      48             : #define AP_FOLLOW_TIMEOUT          3     // position estimate timeout in seconds
      49             : #define AP_FOLLOW_ESTIMATE_TIMEOUT_MS 10000 // discard the target estimate if we have not heard from the target in 10 seconds
      50             : 
      51             : #define AP_FOLLOW_OFFSET_TYPE_NED       0   // offsets are in north-east-down frame
      52             : #define AP_FOLLOW_OFFSET_TYPE_RELATIVE  1   // offsets are relative to lead vehicle's heading
      53             : 
      54             : #define AP_FOLLOW_POS_P_DEFAULT 0.1f    // position error gain default
      55             : 
      56             : #if APM_BUILD_TYPE(APM_BUILD_ArduPlane)
      57             :  #define AP_FOLLOW_ALT_TYPE_DEFAULT static_cast<float>(Location::AltFrame::ABSOLUTE)
      58             :  #define AP_FOLLOW_DIST_MAX_DEFAULT 0    // zero = ignored
      59             : #else
      60             :  #define AP_FOLLOW_ALT_TYPE_DEFAULT static_cast<float>(Location::AltFrame::ABOVE_HOME)
      61             :  #define AP_FOLLOW_DIST_MAX_DEFAULT 100
      62             : #endif
      63             : 
      64             : 
      65             : //==============================================================================
      66             : // Constructor
      67             : //==============================================================================
      68             : 
      69             : AP_Follow *AP_Follow::_singleton;
      70             : 
      71             : // table of user settable parameters
      72             : const AP_Param::GroupInfo AP_Follow::var_info[] = {
      73             : 
      74             :     // @Param: _ENABLE
      75             :     // @DisplayName: Follow enable/disable
      76             :     // @Description: Enabled/disable following a target
      77             :     // @Values: 0:Disabled,1:Enabled
      78             :     // @User: Standard
      79             :     AP_GROUPINFO_FLAGS("_ENABLE", 1, AP_Follow, _enabled, 0, AP_PARAM_FLAG_ENABLE),
      80             : 
      81             :     // 2 is reserved for TYPE parameter
      82             : 
      83             :     // @Param: _SYSID
      84             :     // @DisplayName: Follow target's mavlink system id
      85             :     // @Description: Follow target's mavlink system id. Zero means no target has been selected and following is inactive.
      86             :     // @Range: 0 255
      87             :     // @User: Standard
      88             :     AP_GROUPINFO("_SYSID", 3, AP_Follow, _sysid, 0),
      89             : 
      90             :     // 4 is reserved for MARGIN parameter
      91             : 
      92             :     // @Param: _DIST_MAX
      93             :     // @DisplayName: Follow distance maximum
      94             :     // @Description: Follow distance maximum. If exceeded, the follow estimate will be considered invalid. If zero there is no maximum.
      95             :     // @Units: m
      96             :     // @Range: 0 1000
      97             :     // @User: Standard
      98             :     AP_GROUPINFO("_DIST_MAX", 5, AP_Follow, _dist_max_m, AP_FOLLOW_DIST_MAX_DEFAULT),
      99             : 
     100             :     // @Param: _OFS_TYPE
     101             :     // @DisplayName: Follow offset type
     102             :     // @Description: Follow offset type
     103             :     // @Values: 0:North-East-Down, 1:Relative to lead vehicle heading
     104             :     // @User: Standard
     105             :     AP_GROUPINFO("_OFS_TYPE", 6, AP_Follow, _offset_type, AP_FOLLOW_OFFSET_TYPE_NED),
     106             : 
     107             :     // @Param: _OFS_X
     108             :     // @DisplayName: Follow offsets in meters north/forward
     109             :     // @Description: Follow offsets in meters north/forward. If positive, this vehicle fly ahead or north of lead vehicle.  Depends on FOLL_OFS_TYPE
     110             :     // @Range: -100 100
     111             :     // @Units: m
     112             :     // @Increment: 1
     113             :     // @User: Standard
     114             : 
     115             :     // @Param: _OFS_Y
     116             :     // @DisplayName: Follow offsets in meters east/right
     117             :     // @Description: Follow offsets in meters east/right. If positive, this vehicle will fly to the right or east of lead vehicle.  Depends on FOLL_OFS_TYPE
     118             :     // @Range: -100 100
     119             :     // @Units: m
     120             :     // @Increment: 1
     121             :     // @User: Standard
     122             : 
     123             :     // @Param: _OFS_Z
     124             :     // @DisplayName: Follow offsets in meters down
     125             :     // @Description: Follow offsets in meters down. If positive, this vehicle will fly below the lead vehicle
     126             :     // @Range: -100 100
     127             :     // @Units: m
     128             :     // @Increment: 1
     129             :     // @User: Standard
     130             :     AP_GROUPINFO("_OFS", 7, AP_Follow, _offset_m, 0),
     131             : 
     132             : #if !(APM_BUILD_TYPE(APM_BUILD_Rover))
     133             :     // @Param: _YAW_BEHAVE
     134             :     // @DisplayName: Follow yaw behaviour
     135             :     // @Description: Follow yaw behaviour
     136             :     // @Values: 0:None,1:Face Lead Vehicle,2:Same as Lead vehicle,3:Direction of Flight
     137             :     // @User: Standard
     138             :     AP_GROUPINFO("_YAW_BEHAVE", 8, AP_Follow, _yaw_behave, 1),
     139             : #endif
     140             : 
     141             :     // @Param: _POS_P
     142             :     // @DisplayName: Follow position error P gain
     143             :     // @Description: Follow position error P gain. Converts the difference between desired vertical speed and actual speed into a desired acceleration that is passed to the throttle acceleration controller
     144             :     // @Range: 0.01 1.00
     145             :     // @Increment: 0.01
     146             :     // @User: Standard
     147             :     AP_SUBGROUPINFO(_p_pos, "_POS_", 9, AP_Follow, AC_P),
     148             : 
     149             : #if !(APM_BUILD_TYPE(APM_BUILD_Rover)) 
     150             :     // @Param: _ALT_TYPE
     151             :     // @DisplayName: Follow altitude type
     152             :     // @Description: Follow altitude type
     153             :     // @Values: 0:absolute, 1:relative, 3:terrain
     154             :     // @User: Standard
     155             :     AP_GROUPINFO("_ALT_TYPE", 10, AP_Follow, _alt_type, AP_FOLLOW_ALT_TYPE_DEFAULT),
     156             : #endif
     157             : 
     158             :     // @Param: _OPTIONS
     159             :     // @DisplayName: Follow options
     160             :     // @Description: Follow options bitmask
     161             :     // @Values: 0:None,1: Mount Follows lead vehicle on mode enter
     162             :     // @User: Standard
     163             :     AP_GROUPINFO("_OPTIONS", 11, AP_Follow, _options, 0),
     164             : 
     165             :     // @Param: _ACCEL_NE
     166             :     // @DisplayName: Acceleration limit for the horizontal kinematic input shaping
     167             :     // @Description: Acceleration limit of the horizontal kinematic path generation used to determine how quickly the estimate varies in velocity
     168             :     // @Range: 0 10
     169             :     // @Units: m/s/s
     170             :     // @User: Advanced
     171             :     AP_GROUPINFO("_ACCEL_NE", 12, AP_Follow, _accel_max_ne_mss, 5.0),
     172             : 
     173             :     // @Param: _JERK_NE
     174             :     // @DisplayName: Jerk limit for the horizontal kinematic input shaping
     175             :     // @Description: Jerk limit of the horizontal kinematic path generation used to determine how quickly the estimate varies in acceleration
     176             :     // @Range: 0 20
     177             :     // @Units: m/s/s/s
     178             :     // @User: Advanced
     179             :     AP_GROUPINFO("_JERK_NE", 13, AP_Follow, _jerk_max_ne_msss, 10.0),
     180             : 
     181             :     // @Param: _ACCEL_D
     182             :     // @DisplayName: Acceleration limit for the vertical kinematic input shaping
     183             :     // @Description: Acceleration limit of the vertical kinematic path generation used to determine how quickly the estimate varies in velocity
     184             :     // @Range: 0 10
     185             :     // @Units: m/s/s
     186             :     // @User: Advanced
     187             :     AP_GROUPINFO("_ACCEL_D", 14, AP_Follow, _accel_max_d_mss, 5.0),
     188             : 
     189             :     // @Param: _JERK_D
     190             :     // @DisplayName: Jerk limit for the vertical kinematic input shaping
     191             :     // @Description: Jerk limit of the vertical kinematic path generation used to determine how quickly the estimate varies in acceleration
     192             :     // @Range: 0 20
     193             :     // @Units: m/s/s/s
     194             :     // @User: Advanced
     195             :     AP_GROUPINFO("_JERK_D", 15, AP_Follow, _jerk_max_d_msss, 10.0),
     196             : 
     197             :     // @Param: _ACCEL_H
     198             :     // @DisplayName: Angular acceleration limit for the heading kinematic input shaping
     199             :     // @Description: Angular acceleration limit of the heading kinematic path generation used to determine how quickly the estimate varies in angular velocity
     200             :     // @Range: 0 720
     201             :     // @Units: deg/s/s
     202             :     // @User: Advanced
     203             :     AP_GROUPINFO("_ACCEL_H", 16, AP_Follow, _accel_max_h_degss, 360.0),
     204             : 
     205             :     // @Param: _JERK_H
     206             :     // @DisplayName: Angular jerk limit for the heading kinematic input shaping
     207             :     // @Description: Angular jerk limit of the heading kinematic path generation used to determine how quickly the estimate varies in angular acceleration
     208             :     // @Range: 0 1440
     209             :     // @Units: deg/s/s/s
     210             :     // @User: Advanced
     211             :     AP_GROUPINFO("_JERK_H", 17, AP_Follow, _jerk_max_h_degsss, 720.0),
     212             : 
     213             :     // @Param: _TIMEOUT
     214             :     // @DisplayName: Follow timeout
     215             :     // @Description: Follow position update from lead - timeout after x seconds
     216             :     // @User: Standard
     217             :     // @Units: s
     218             :     AP_GROUPINFO("_TIMEOUT", 18, AP_Follow, _timeout, AP_FOLLOW_TIMEOUT),
     219             : 
     220             :     AP_GROUPEND
     221             : };
     222             : 
     223             : // Constructor for AP_Follow. Initializes the position P-controller and sets up parameter defaults.
     224        1978 : AP_Follow::AP_Follow() :
     225        1978 :     _p_pos(AP_FOLLOW_POS_P_DEFAULT)
     226             : {
     227        1978 :     _singleton = this;
     228        1978 :     AP_Param::setup_object_defaults(this, var_info);
     229        1978 : }
     230             : 
     231             : 
     232             : //==============================================================================
     233             : // Target Estimation Update Functions
     234             : //==============================================================================
     235             : 
     236             : // Projects and updates the estimated target position, velocity, and heading based on last known data and configured input shaping.
     237    39419043 : void AP_Follow::update_estimates()
     238             : {
     239    39419043 :     WITH_SEMAPHORE(_follow_sem);
     240             : 
     241             :     // if we do not hold fresh data from the configured target, invalidate the estimate.
     242             :     // this cannot gate on have_target() because that tests _estimate_valid, which is
     243             :     // cleared below, and the estimate could then never be rebuilt
     244    39419043 :     if (!have_target_data()) {
     245    39310420 :         clear_dist_and_bearing_to_target();
     246    39310420 :         _estimate_valid = false;
     247    39310420 :         return;
     248             :     }
     249             : 
     250             :     // check for timeout
     251      108623 :     if ((_last_location_update_ms == 0) || ((AP_HAL::millis() - _last_location_update_ms) > (uint32_t)(_timeout * 1000.0f))) {
     252       51210 :         clear_dist_and_bearing_to_target();
     253       51210 :         _estimate_valid = false;
     254       51210 :         return;
     255             :     }
     256             : 
     257       57413 :     const uint32_t now = AP_HAL::millis();
     258             : 
     259             :     // calculate time since last location update in seconds
     260       57413 :     const float dt = (now - _last_location_update_ms) * 0.001f;
     261             : 
     262             :     // project target's position and velocity forward using simple kinematics
     263       57413 :     Vector3f delta_pos_m = _target_vel_ned_ms * dt + _target_accel_ned_mss * 0.5f * sq(dt);
     264       57413 :     Vector3f delta_vel_ms = _target_accel_ned_mss * dt;
     265       57413 :     float delta_heading_rad = radians(_target_heading_rate_degs) * dt;
     266             : 
     267             :     // calculate time since last estimation update
     268       57413 :     const float e_dt = (now - _last_estimation_update_ms) * 0.001f;
     269             : 
     270      114826 :     const bool valid_kinematic_params = (_accel_max_ne_mss > 0.0f) && (_jerk_max_ne_msss > 0.0f) &&
     271       57413 :                                 (_accel_max_d_mss > 0.0f) && (_jerk_max_d_msss > 0.0f) &&
     272      114826 :                                 (_accel_max_h_degss > 0.0f) && (_jerk_max_h_degsss > 0.0f);
     273             :     
     274       57413 :     if (_estimate_valid && valid_kinematic_params) {
     275             :         // update X/Y position, velocity, acceleration with shaping
     276       57407 :         update_pos_vel_accel_xy(_estimate_pos_ned_m.xy(), _estimate_vel_ned_ms.xy(), _estimate_accel_ned_mss.xy(), e_dt, Vector2f(), Vector2f(), Vector2f());
     277             : 
     278             :         // integrate the Z axis estimate forward by e_dt
     279       57407 :         update_pos_vel_accel(_estimate_pos_ned_m.z, _estimate_vel_ned_ms.z, _estimate_accel_ned_mss.z, e_dt, 0.0, 0.0, 0.0);
     280             : 
     281             :         // apply horizontal shaping to refine estimate toward projected target state
     282      114814 :         shape_pos_vel_accel_xy(_target_pos_ned_m.xy() + delta_pos_m.xy().topostype(), _target_vel_ned_ms.xy() + delta_vel_ms.xy(), _target_accel_ned_mss.xy(),
     283       57407 :                                _estimate_pos_ned_m.xy(), _estimate_vel_ned_ms.xy(), _estimate_accel_ned_mss.xy(),
     284             :                                0.0, _accel_max_ne_mss, _jerk_max_ne_msss, e_dt, false);
     285             : 
     286             :         // apply vertical shaping to refine the Z estimate toward the projected
     287             :         // target state (jerk-limited, tuned via FOLL_ACCEL_D / FOLL_JERK_D)
     288       57407 :         shape_pos_vel_accel(_target_pos_ned_m.z + delta_pos_m.z, _target_vel_ned_ms.z + delta_vel_ms.z, _target_accel_ned_mss.z,
     289       57407 :                             _estimate_pos_ned_m.z, _estimate_vel_ned_ms.z, _estimate_accel_ned_mss.z,
     290       57407 :                             0.0, 0.0, -_accel_max_d_mss, _accel_max_d_mss, _jerk_max_d_msss, e_dt, false);
     291             : 
     292             :         // apply angular shaping for heading estimate
     293      172221 :         shape_angle_vel_accel(radians(_target_heading_deg) + delta_heading_rad, radians(_target_heading_rate_degs), 0.0,
     294       57407 :                               _estimate_heading_rad, _estimate_heading_rate_rads, _estimate_heading_accel_radss,
     295       57407 :                               0.0, 0.0, radians(_accel_max_h_degss),
     296       57407 :                               radians(_jerk_max_h_degsss), e_dt, false);
     297             : 
     298             :         // update heading angle separately to maintain proper wrapping [-PI, PI]
     299       57407 :         postype_t estimate_heading_rad = _estimate_heading_rad;
     300       57407 :         update_pos_vel_accel(estimate_heading_rad, _estimate_heading_rate_rads, _estimate_heading_accel_radss, e_dt, 0.0, 0.0, 0.0);
     301       57407 :         _estimate_heading_rad = wrap_PI(float(estimate_heading_rad));
     302       57407 :     } else {
     303             :         // no valid estimate yet: initialise from latest target position
     304           6 :         _estimate_pos_ned_m = _target_pos_ned_m + delta_pos_m.topostype();
     305           6 :         _estimate_vel_ned_ms = _target_vel_ned_ms + delta_vel_ms;
     306           6 :         _estimate_accel_ned_mss = _target_accel_ned_mss;
     307           6 :         _estimate_heading_rad = radians(_target_heading_deg) + delta_heading_rad;
     308           6 :         _estimate_heading_rate_rads = radians(_target_heading_rate_degs);
     309           6 :         _estimate_heading_accel_radss = 0.0;
     310           6 :         _estimate_valid = true;
     311             :     }
     312             : 
     313       57413 :     Vector3f offset_m = _offset_m.get();
     314             : 
     315             :     // calculate estimated position and velocity with offsets applied
     316       57413 :     if (offset_m.is_zero() || (_offset_type == AP_FOLLOW_OFFSET_TYPE_NED)) {
     317             :         // offsets are in NED frame: simple addition
     318       39822 :         _ofs_estimate_pos_ned_m = _estimate_pos_ned_m + offset_m.topostype();
     319       39822 :         _ofs_estimate_vel_ned_ms = _estimate_vel_ned_ms;
     320       39822 :         _ofs_estimate_accel_ned_mss = _estimate_accel_ned_mss;
     321             :     } else {
     322             :         // offsets are in FRD frame: rotate the FRD offset into NED using the target's heading
     323       17591 :         offset_m.xy().rotate(_estimate_heading_rad);
     324       17591 :         _ofs_estimate_pos_ned_m = _estimate_pos_ned_m + offset_m.topostype();
     325             :         // seed vel/accel with the target's own translational vel/accel; rotational
     326             :         // contributions from the offset rotating with the target are added below
     327       17591 :         _ofs_estimate_vel_ned_ms = _estimate_vel_ned_ms;
     328       17591 :         _ofs_estimate_accel_ned_mss = _estimate_accel_ned_mss;
     329             :         // When heading rate/accel are available (kinematic shaping active), the offset
     330             :         // point is fixed in a frame rotating about the NED down-axis at the target's
     331             :         // heading rate (ω) and heading accel (α), at radius r = offset_m, giving:
     332             :         //   v_rot = ω × r
     333             :         //   a_rot = α × r + ω × (ω × r)   (Euler/tangential term + centripetal term)
     334             :         // These are added on top of the target's translational vel/accel set above.
     335       17591 :         if (valid_kinematic_params) {
     336       17591 :             const Vector3f angular_vel{0.0f, 0.0f, _estimate_heading_rate_rads};       // ω: heading rate about NED down-axis
     337       17591 :             const Vector3f angular_accel{0.0f, 0.0f, _estimate_heading_accel_radss};   // α: heading angular acceleration
     338       17591 :             const Vector3f vel_due_to_rotation = angular_vel.cross(offset_m);          // ω × r
     339       17591 :             _ofs_estimate_vel_ned_ms += vel_due_to_rotation;
     340       17591 :             _ofs_estimate_accel_ned_mss += angular_accel.cross(offset_m) + angular_vel.cross(vel_due_to_rotation);  // α × r + ω × (ω × r)
     341             :         }
     342             :     }
     343             : 
     344             :     // update the distance and bearing to the target
     345       57413 :     update_dist_and_bearing_to_target();
     346             : 
     347       57413 :     _last_estimation_update_ms = now;
     348             : 
     349             :     // Check if the target is within the maximum distance
     350       57413 :     Vector3p current_position_ned_m;
     351       57413 :     if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {
     352             :         // no idea where we are; knowing where other things are won't help.
     353           0 :         _estimate_valid = false;
     354           0 :         return;
     355             :     }
     356       57413 :     const Vector3p dist_vec_ned_m = _target_pos_ned_m - current_position_ned_m;
     357             :     // If _dist_max_m is not positive, we don't check the distance
     358       57413 :     if (is_positive(_dist_max_m.get()) && (dist_vec_ned_m.length() > _dist_max_m)) {
     359             :         // target is too far away, mark the estimate invalid
     360           0 :         _estimate_valid = false;
     361             :     }
     362    39419043 : }
     363             : 
     364             : 
     365             : //==============================================================================
     366             : // Target Information Retrieval Functions
     367             : //==============================================================================
     368             : 
     369             : // Retrieves the estimated target position, velocity, and acceleration in the NED frame (relative to origin).
     370       15272 : bool AP_Follow::get_target_pos_vel_accel_NED_m(Vector3p &pos_ned_m, Vector3f &vel_ned_ms, Vector3f &accel_ned_mss) const
     371             : {
     372       15272 :     if (!have_target()) {
     373           0 :         return false;
     374             :     }
     375             : 
     376       15272 :     pos_ned_m = _estimate_pos_ned_m;
     377       15272 :     vel_ned_ms = _estimate_vel_ned_ms;
     378       15272 :     accel_ned_mss = _estimate_accel_ned_mss;
     379             : 
     380       15272 :     return true;
     381             : }
     382             : 
     383             : // Retrieves the estimated target position, velocity, and acceleration in the NED frame, including configured offsets.
     384       15437 : bool AP_Follow::get_ofs_pos_vel_accel_NED_m(Vector3p &pos_ofs_ned_m, Vector3f &vel_ofs_ned_ms, Vector3f &accel_ofs_ned_mss) const
     385             : {
     386       15437 :     if (!have_target()) {
     387         165 :         return false;
     388             :     }
     389             : 
     390       15272 :     pos_ofs_ned_m = _ofs_estimate_pos_ned_m;
     391       15272 :     vel_ofs_ned_ms = _ofs_estimate_vel_ned_ms;
     392       15272 :     accel_ofs_ned_mss = _ofs_estimate_accel_ned_mss;
     393             : 
     394       15272 :     return true;
     395             : }
     396             : 
     397             : // Retrieves distance vectors (with and without configured offsets) and the target’s velocity, all in the NED frame.
     398           0 : bool AP_Follow::get_target_dist_and_vel_NED_m(Vector3f &dist_ned, Vector3f &dist_with_offs, Vector3f &vel_ned)
     399             : {
     400           0 :     WITH_SEMAPHORE(_follow_sem);
     401             :     
     402           0 :     if (!have_target()) {
     403           0 :         return false;
     404             :     }
     405             : 
     406           0 :     Vector3p current_position_ned_m;
     407           0 :     if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {
     408           0 :         return false;
     409             :     }
     410             : 
     411           0 :     const Vector3p dist_vec_ned_m = _estimate_pos_ned_m - current_position_ned_m;
     412           0 :     const Vector3p ofs_dist_vec = _ofs_estimate_pos_ned_m - current_position_ned_m;
     413           0 :     dist_ned = dist_vec_ned_m.tofloat();
     414           0 :     dist_with_offs = ofs_dist_vec.tofloat();
     415           0 :     vel_ned = _ofs_estimate_vel_ned_ms;
     416             : 
     417           0 :     return true;
     418           0 : }
     419             : 
     420             : // Retrieves the estimated target heading and heading rate in radians.
     421           0 : bool AP_Follow::get_heading_heading_rate_rad(float &heading_rad, float &heading_rate_rads) const
     422             : {
     423           0 :     if (!have_target()) {
     424           0 :         return false;
     425             :     }
     426             : 
     427             :     // return latest heading estimate
     428           0 :     heading_rad = _estimate_heading_rad;
     429           0 :     heading_rate_rads = _estimate_heading_rate_rads;
     430           0 :     return true;
     431             : }
     432             : 
     433             : // Retrieves the target's estimated global location and estimated velocity
     434         812 : bool AP_Follow::get_target_location_and_velocity(Location &loc, Vector3f &vel_ned)
     435             : {
     436         812 :     WITH_SEMAPHORE(_follow_sem);
     437             : 
     438         812 :     if (!have_target()) {
     439           6 :         return false;
     440             :     }
     441             : 
     442         806 :     if (!AP::ahrs().get_location_from_origin_offset_NED(loc, _estimate_pos_ned_m)) {
     443           0 :         return false;
     444             :     }
     445         806 :     vel_ned = _estimate_vel_ned_ms;
     446             : 
     447             :     // The frame requested by FOLL_ALT_TYPE may not be the frame of location returned by ahrs. 
     448             :     // Make sure we give the caller the frame they have asked for.
     449         806 :     return loc.change_alt_frame(_alt_type);
     450         812 : }
     451             : 
     452             : // Retrieves the target's estimated global location including configured offsets, and estimated velocity,  for LUA bindings.
     453        2828 : bool AP_Follow::get_target_location_and_velocity_ofs(Location &loc, Vector3f &vel_ned)
     454             : {
     455        2828 :     WITH_SEMAPHORE(_follow_sem);
     456             : 
     457        2828 :     if (!have_target()) {
     458          10 :         return false;
     459             :     }
     460        2818 :     if (!AP::ahrs().get_location_from_origin_offset_NED(loc, _ofs_estimate_pos_ned_m)) {
     461           0 :         return false;
     462             :     }
     463             : 
     464        2818 :     vel_ned = _ofs_estimate_vel_ned_ms;
     465             : 
     466             :     // give the caller the frame FOLL_ALT_TYPE asks for
     467        2818 :     return loc.change_alt_frame(_alt_type);
     468        2828 : }
     469             : 
     470             : // Retrieves the estimated target heading in degrees (0° = North, 90° = East) for LUA bindings.
     471       17899 : bool AP_Follow::get_target_heading_deg(float &heading_deg)
     472             : {
     473       17899 :     WITH_SEMAPHORE(_follow_sem);
     474             :     
     475       17899 :     if (!have_target()) {
     476           0 :         return false;
     477             :     }
     478             : 
     479             :     // return latest heading estimate
     480       17899 :     heading_deg = degrees(_estimate_heading_rad);
     481       17899 :     return true;
     482       17899 : }
     483             : 
     484             : // Retrieves the estimated target heading in degrees (0° = North, 90° = East) for LUA bindings.
     485       15272 : bool AP_Follow::get_target_heading_rate_degs(float &heading_rate_degs)
     486             : {
     487       15272 :     WITH_SEMAPHORE(_follow_sem);
     488             :     
     489       15272 :     if (!have_target()) {
     490           0 :         return false;
     491             :     }
     492             : 
     493             :     // return latest heading estimate
     494       15272 :     heading_rate_degs = degrees(_estimate_heading_rate_rads);
     495       15272 :     return true;
     496       15272 : }
     497             : 
     498             : 
     499             : //==============================================================================
     500             : // MAVLink Message Handling
     501             : //==============================================================================
     502             : 
     503             : // Handles incoming MAVLink messages to update the target's position, velocity, and heading.
     504     2637438 : void AP_Follow::handle_msg(const mavlink_message_t &msg)
     505             : {
     506             :     // the LUA accessors read this state from the scripting thread
     507     2637438 :     WITH_SEMAPHORE(_follow_sem);
     508             : 
     509             :     // FOLL_SYSID no longer matches the system that supplied the data we hold. Forget the old
     510             :     // target's message type and update time. This must run before the switch below, which
     511             :     // consults _using_follow_target to decide whether GLOBAL_POSITION_INT is still wanted.
     512     2637438 :     if (_sysid != _sysid_of_data) {
     513        5697 :         _using_follow_target = false;
     514        5697 :         _last_location_update_ms = 0;   // 0 = nothing heard from this target
     515             :     }
     516             : 
     517             :     // Invalidate the estimate if no position update has been received within the timeout period.
     518     2644083 :     if ((_last_location_update_ms == 0) ||
     519        6645 :         (AP_HAL::millis() - _last_location_update_ms > AP_FOLLOW_ESTIMATE_TIMEOUT_MS)) {
     520     2631320 :         _estimate_valid = false;   // mark estimate as invalid
     521     2631320 :         _using_follow_target = false; // reset follow-target usage flag
     522             :     }
     523             : 
     524     2637438 :     if (!should_handle_message(msg)) {
     525             :         // ignore message if filtering rules reject it (e.g., wrong sysid)
     526     2632005 :         return;
     527             :     }
     528             : 
     529             :     // decode MAVLink message
     530        5433 :     bool updated = false;
     531             : 
     532        5433 :     switch (msg.msgid) {
     533         861 :     case MAVLINK_MSG_ID_GLOBAL_POSITION_INT: {
     534             :         // if we are using follow_target, ignore global_position_int messages
     535         861 :         if (_using_follow_target) {
     536           0 :             return;
     537             :         }
     538             :         // handle standard global position messages
     539         861 :         updated = handle_global_position_int_message(msg);
     540         861 :         break;
     541             :     }
     542          58 :     case MAVLINK_MSG_ID_FOLLOW_TARGET: {
     543             :         // handle follow-target specific messages
     544          58 :         updated = handle_follow_target_message(msg);
     545          58 :         break;
     546             :     }
     547             :     }
     548             : 
     549        5433 :     if (updated) {
     550             :         // reset the estimate on a large error or a change of source system
     551         812 :         if (estimate_error_too_large() || (_sysid_of_data != msg.sysid)) {
     552           6 :             _estimate_valid = false;
     553             :         }
     554             : 
     555             :         // record the system that supplied this update.  this is msg.sysid rather than
     556             :         // _sysid because msg.sysid is the value should_handle_message() validated, and
     557             :         // _sysid can be written by the scripting thread part way through this function
     558         812 :         _sysid_of_data = msg.sysid;
     559             : 
     560             : #if HAL_LOGGING_ENABLED
     561             :         // log current follow diagnostic data
     562         812 :         Log_Write_FOLL();
     563             : #endif
     564             :     }
     565     2637438 : }
     566             : 
     567             : // Returns true if the incoming MAVLink message should be processed for target updates.
     568     2637438 : bool AP_Follow::should_handle_message(const mavlink_message_t &msg) const
     569             : {
     570             :     // exit immediately if not enabled
     571     2637438 :     if (!_enabled) {
     572     2618031 :         return false;
     573             :     }
     574             : 
     575             :     // sysid 0 is the broadcast system and is never a valid target.  without this
     576             :     // a sender broadcasting as sysid 0 would compare equal to an unset _sysid
     577       19407 :     if (msg.sysid == 0) {
     578           0 :         return false;
     579             :     }
     580             : 
     581             :     // skip our own messages
     582       19407 :     if (msg.sysid == mavlink_system.sysid) {
     583           0 :         return false;
     584             :     }
     585             : 
     586             :     // skip message if not from our target.  a _sysid of zero means no target
     587             :     // has been selected, so no message is ever accepted
     588       19407 :     if (msg.sysid != _sysid) {
     589       13974 :         return false;
     590             :     }
     591             : 
     592        5433 :     return true;
     593             : }
     594             : 
     595             : // Checks whether the current estimate should be reset based on position and velocity errors.
     596         812 : bool AP_Follow::estimate_error_too_large() const
     597             : {
     598         812 :     const float timeout_sec = _timeout;
     599             : 
     600             :     // Calculate position thresholds based on maximum acceleration then deceleration for the timeout duration
     601         812 :     const float pos_thresh_horiz_m = _accel_max_ne_mss.get() * sq(timeout_sec * 0.5);
     602         812 :     const float pos_thresh_vert_m  = _accel_max_d_mss.get()  * sq(timeout_sec * 0.5);
     603             : 
     604             :     // Calculate velocity thresholds using the new helper function
     605         812 :     const float vel_thresh_horiz_ms = calc_max_velocity_change(_accel_max_ne_mss.get(), _jerk_max_ne_msss.get(), timeout_sec);
     606         812 :     const float vel_thresh_vert_ms  = calc_max_velocity_change(_accel_max_d_mss.get(), _jerk_max_d_msss.get(), timeout_sec);
     607             : 
     608             :     // Calculate current position and velocity errors
     609         812 :     const Vector3f pos_error = _estimate_pos_ned_m.tofloat() - _target_pos_ned_m.tofloat();
     610         812 :     const Vector3f vel_error = _estimate_vel_ned_ms - _target_vel_ned_ms;
     611             : 
     612         812 :     const Vector2f pos_error_xy = pos_error.xy();
     613         812 :     const float pos_error_z = pos_error.z;
     614         812 :     const Vector2f vel_error_xy = vel_error.xy();
     615         812 :     const float vel_error_z = vel_error.z;
     616             : 
     617             :     // Check horizontal and vertical separately
     618         812 :     const bool pos_horiz_bad = pos_error_xy.length() > pos_thresh_horiz_m;
     619         812 :     const bool vel_horiz_bad = vel_error_xy.length() > vel_thresh_horiz_ms;
     620         812 :     const bool pos_vert_bad = fabsf(pos_error_z) > pos_thresh_vert_m;
     621         812 :     const bool vel_vert_bad = fabsf(vel_error_z) > vel_thresh_vert_ms;
     622             : 
     623         812 :     return pos_horiz_bad || vel_horiz_bad || pos_vert_bad || vel_vert_bad;
     624             : }
     625             : 
     626             : // Calculates max velocity change under trapezoidal or triangular acceleration profile (jerk-limited).
     627        1624 : float AP_Follow::calc_max_velocity_change(float accel_max, float jerk_max, float timeout_sec) const
     628             : {
     629             :     // a non-positive jerk limit means "no jerk limit": acceleration can be
     630             :     // applied instantly, so the profile is a pure trapezoid. Guarding here also
     631             :     // avoids a divide-by-zero when the FOLL_JERK_* parameter is set to zero.
     632        1624 :     if (!is_positive(jerk_max)) {
     633           0 :         return accel_max * timeout_sec;
     634             :     }
     635        1624 :     const float t_jerk = accel_max / jerk_max;
     636        1624 :     const float t_total_jerk = 2.0f * t_jerk;
     637             : 
     638        1624 :     if (timeout_sec >= t_total_jerk) {
     639             :         // time to ramp up, constant accel phase, and ramp down
     640        1624 :         const float t_const = timeout_sec - t_total_jerk;
     641        1624 :         const float delta_v_jerk = 0.5f * accel_max * t_jerk;
     642        1624 :         const float delta_v_const = accel_max * t_const;
     643        1624 :         return 2.0f * delta_v_jerk + delta_v_const;
     644             :     } else {
     645             :         // timeout too short: pure triangle profile
     646           0 :         const float t_half = timeout_sec * 0.5f;
     647           0 :         return jerk_max * sq(t_half);
     648             :     }
     649             : }
     650             : 
     651             : // Decodes a GLOBAL_POSITION_INT MAVLink message to update the target’s position, velocity, and heading.
     652         861 : bool AP_Follow::handle_global_position_int_message(const mavlink_message_t &msg)
     653             : {
     654             :     // decode GLOBAL_POSITION_INT message into packet struct
     655             :     mavlink_global_position_int_t packet;
     656         861 :     mavlink_msg_global_position_int_decode(&msg, &packet);
     657             : 
     658             :     // ignore message if latitude and longitude are exactly zero (invalid GPS fix)
     659         861 :     if ((packet.lat == 0 && packet.lon == 0)) {
     660           0 :         return false;
     661             :     }
     662             : 
     663         861 :     Location target_location;
     664         861 :     target_location.lat = packet.lat;
     665         861 :     target_location.lng = packet.lon;
     666             : 
     667         861 :     switch((Location::AltFrame)_alt_type) {
     668         767 :         case Location::AltFrame::ABSOLUTE:
     669         767 :             target_location.set_alt_cm(packet.alt * 0.1, Location::AltFrame::ABSOLUTE);
     670         767 :             break;
     671          94 :         case Location::AltFrame::ABOVE_HOME:
     672          94 :             target_location.set_alt_cm(packet.relative_alt * 0.1, Location::AltFrame::ABOVE_HOME);
     673          94 :             break;
     674             : #if APM_BUILD_TYPE(APM_BUILD_ArduPlane) || APM_BUILD_TYPE(APM_BUILD_ArduCopter)
     675           0 :         case Location::AltFrame::ABOVE_TERRAIN:
     676             :             /// Altitude comes in as AMSL
     677           0 :             target_location.set_alt_cm(packet.alt * 0.1, Location::AltFrame::ABSOLUTE);
     678             :             // convert the incoming altitude to terrain altitude, but fail if there is no terrain data available
     679           0 :             if (!target_location.change_alt_frame(Location::AltFrame::ABOVE_TERRAIN)) {
     680           0 :                 return false;
     681             :             };
     682           0 :             break;
     683             : #endif
     684           0 :         default:
     685             :             // don't process the packet if the _alt_type is invalid
     686           0 :             return false;
     687             :     }
     688             :     
     689             :     // convert global location to local NED frame position
     690         861 :     Vector3p target_pos_neu_m;
     691         861 :     if (!target_location.get_vector_from_origin_NEU_m(target_pos_neu_m)) {
     692         107 :         return false;
     693             :     }
     694             : 
     695         754 :     if (packet.hdg <= 36000) {
     696             :         // valid heading field available (in centi-degrees)
     697         754 :         _target_heading_deg = packet.hdg * 0.01f;
     698           0 :     } else if (_offset_type == AP_FOLLOW_OFFSET_TYPE_RELATIVE) {
     699             :         // heading missing but relative offset mode requires heading -> reject
     700           0 :         return false;
     701             :     } else {
     702             :         // no heading available: set heading rate to zero
     703           0 :         _target_heading_rate_degs = 0.0f;
     704             :     }
     705             : 
     706         754 :     _target_pos_ned_m.xy() = target_pos_neu_m.xy(); 
     707         754 :     _target_pos_ned_m.z = -target_pos_neu_m.z;
     708             : 
     709             :     // decode target velocity components (cm/s converted to m/s)
     710         754 :     _target_vel_ned_ms.x = packet.vx * 0.01f; // velocity north
     711         754 :     _target_vel_ned_ms.y = packet.vy * 0.01f; // velocity east
     712         754 :     _target_vel_ned_ms.z = packet.vz * 0.01f; // velocity down
     713             : 
     714             :     // target acceleration not available in GLOBAL_POSITION_INT
     715         754 :     _target_accel_ned_mss.zero();
     716             : 
     717             :     // apply jitter-corrected timestamp to this update
     718         754 :     _last_location_update_ms = _jitter.correct_offboard_timestamp_msec(packet.time_boot_ms, AP_HAL::millis());
     719             : 
     720         754 :     return true;
     721             : }
     722             : 
     723             : // Decodes a FOLLOW_TARGET MAVLink message to update the target’s position, velocity, acceleration, and orientation.
     724          58 : bool AP_Follow::handle_follow_target_message(const mavlink_message_t &msg)
     725             : {
     726             :     // decode FOLLOW_TARGET message into packet struct
     727             :     mavlink_follow_target_t packet;
     728          58 :     mavlink_msg_follow_target_decode(&msg, &packet);
     729             : 
     730             :     // ignore message if latitude and longitude are exactly zero (invalid GPS fix)
     731          58 :     if ((packet.lat == 0 && packet.lon == 0)) {
     732           0 :         return false;
     733             :     }
     734             : 
     735             :     // require that at least position is estimated (bit 0 of est_capabilities)
     736          58 :     if ((packet.est_capabilities & (1<<0)) == 0) {
     737           0 :         return false;
     738             :     }
     739             : 
     740             :     // build Location object from latitude, longitude, and altitude (alt in meters)
     741             :     const Location target_location {
     742             :         packet.lat,
     743             :         packet.lon,
     744          58 :         int32_t(packet.alt * 100),  // convert meters to centimeters
     745             :         Location::AltFrame::ABSOLUTE
     746          58 :     };
     747             : 
     748             :     // convert global location to local NED frame position
     749          58 :     Vector3p target_pos_neu_m;
     750          58 :     if (!target_location.get_vector_from_origin_NEU_m(target_pos_neu_m)) {
     751           0 :         return false;
     752             :     }
     753             : 
     754             :     // adjust Z coordinate to NED frame (NEU altitude -> NED)
     755          58 :     Location origin;
     756          58 :     if (!AP::ahrs().get_origin(origin)) {
     757           0 :         return false;
     758             :     }
     759             : 
     760             :     // decode attitude if available (bit 3 of est_capabilities)
     761          58 :     if (packet.est_capabilities & (1 << 3)) {
     762             :         // reconstruct quaternion from packet
     763          58 :         Quaternion q{packet.attitude_q[0], packet.attitude_q[1], packet.attitude_q[2], packet.attitude_q[3]};
     764             :         float roll, pitch, yaw;
     765          58 :         q.to_euler(roll, pitch, yaw);
     766             : 
     767             :         // store heading in degrees, wrapped 0–360
     768          58 :         _target_heading_deg = wrap_360(degrees(yaw));
     769             : 
     770             :         // transform body rates (roll, pitch, yaw) to earth frame rates
     771          58 :         Matrix3f R;
     772          58 :         q.rotation_matrix(R);
     773          58 :         Vector3f omega_body = Vector3f{packet.rates[0], packet.rates[1], packet.rates[2]};
     774          58 :         Vector3f omega_world = R * omega_body; // rotate rates into earth frame
     775             : 
     776             :         // store heading rate (yaw rate in world frame) in degrees/sec
     777          58 :         _target_heading_rate_degs = degrees(omega_world.z);
     778           0 :     } else if (_offset_type == AP_FOLLOW_OFFSET_TYPE_RELATIVE) {
     779             :         // if attitude unavailable and using relative frame, cannot compute — abort
     780           0 :         return false;
     781             :     } else {
     782             :         // otherwise, default heading rate to zero
     783           0 :         _target_heading_rate_degs = 0.0f;
     784             :     }
     785             : 
     786          58 :     _target_pos_ned_m.xy() = target_pos_neu_m.xy();
     787          58 :     _target_pos_ned_m.z = -packet.alt + origin.alt * 0.01;
     788             : 
     789             :     // decode velocity if available (bit 1 of est_capabilities)
     790          58 :     if (packet.est_capabilities & (1<<1)) {
     791          58 :         _target_vel_ned_ms.x = packet.vel[0]; // velocity north
     792          58 :         _target_vel_ned_ms.y = packet.vel[1]; // velocity east
     793          58 :         _target_vel_ned_ms.z = packet.vel[2]; // velocity down
     794             :     } else {
     795           0 :         _target_vel_ned_ms.zero();
     796             :     }
     797             : 
     798             :     // decode acceleration if available (bit 2 of est_capabilities)
     799          58 :     if (packet.est_capabilities & (1 << 2)) {
     800           0 :         _target_accel_ned_mss.x = packet.acc[0]; // acceleration north
     801           0 :         _target_accel_ned_mss.y = packet.acc[1]; // acceleration east
     802           0 :         _target_accel_ned_mss.z = packet.acc[2]; // acceleration down
     803             :     } else {
     804          58 :         _target_accel_ned_mss.zero();
     805             :     }
     806             : 
     807             :     // apply jitter-corrected timestamp to this update
     808          58 :     _last_location_update_ms = _jitter.correct_offboard_timestamp_msec(packet.timestamp, AP_HAL::millis());
     809             : 
     810             :     // prefer FOLLOW_TARGET over GLOBAL_POSITION_INT from now on
     811          58 :     _using_follow_target = true;
     812             : 
     813          58 :     return true;
     814             : }
     815             : 
     816             : 
     817             : //==============================================================================
     818             : // Offset Initialization and Adjustment Functions
     819             : //==============================================================================
     820             : 
     821             : // Initializes the positional offsets from the target vehicle if not already set.
     822       15437 : void AP_Follow::init_offsets_if_required()
     823             : {
     824             :     // return immediately if offsets have already been set
     825       15437 :     if (!_offset_m.get().is_zero()) {
     826       15437 :         return;
     827             :     }
     828           0 :     _offsets_were_zero = true;
     829             : 
     830           0 :     if (!have_target()) {
     831           0 :         return;
     832             :     }
     833             : 
     834             :     // Check if the target is within the maximum distance
     835           0 :     Vector3p current_position_ned_m;
     836           0 :     if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {
     837           0 :         return;
     838             :     }
     839           0 :     const Vector3f dist_vec_ned_m = (_target_pos_ned_m - current_position_ned_m).tofloat();
     840             : 
     841           0 :     if ((_offset_type == AP_FOLLOW_OFFSET_TYPE_RELATIVE)) {
     842             :         // rotate offset into vehicle-relative frame based on heading
     843           0 :         _offset_m.set(rotate_vector(-dist_vec_ned_m, -degrees(_estimate_heading_rad)));
     844           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Relative follow offset loaded");
     845             :     } else {
     846             :         // initialize offset in NED frame (no heading rotation)
     847           0 :         _offset_m.set(-dist_vec_ned_m);
     848             : 
     849             :         // ensure offset type is set to NED frame if initialized this way
     850           0 :         _offset_type.set(AP_FOLLOW_OFFSET_TYPE_NED);
     851           0 :         GCS_SEND_TEXT(MAV_SEVERITY_INFO, "N-E-D follow offset loaded");
     852             :     }
     853             : }
     854             : 
     855             : // Rotates a 3D vector clockwise by the specified angle (degrees).
     856           0 : Vector3f AP_Follow::rotate_vector(const Vector3f &vec, float angle_deg) const
     857             : {
     858             :     // rotate roll, pitch input from north facing to vehicle's perspective
     859           0 :     Vector3f ret = vec;
     860           0 :     ret.xy().rotate(radians(angle_deg));
     861             : 
     862           0 :     return ret;
     863             : }
     864             : 
     865             : // Resets follow mode offsets to zero if they were automatically initialized.
     866           7 : void AP_Follow::clear_offsets_if_required()
     867             : {
     868           7 :     if (_offsets_were_zero) {
     869           0 :         _offset_m.set(Vector3f());
     870           0 :         _offsets_were_zero = false;
     871             :     }
     872           7 : }
     873             : 
     874             : 
     875             : //==============================================================================
     876             : // Distance and Bearing Management
     877             : //==============================================================================
     878             : 
     879             : // Resets the recorded distance and bearing to the target to zero.
     880    39361630 : void AP_Follow::clear_dist_and_bearing_to_target()
     881             : {
     882    39361630 :     _dist_to_target_m = 0.0f;
     883    39361630 :     _bearing_to_target_deg = 0.0f;
     884    39361630 : }
     885             : 
     886             : // Updates the recorded distance and bearing to the target to zero.
     887       57413 : void AP_Follow::update_dist_and_bearing_to_target()
     888             : {
     889       57413 :     Vector3p current_position_ned_m;
     890       57413 :     if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {
     891             :         // if unable to retrieve local position, clear distance/bearing info
     892           0 :         clear_dist_and_bearing_to_target();
     893             :     } else {
     894             :         // get_relative_position_NED_origin() already returns metres in the NED
     895             :         // frame, matching _ofs_estimate_pos_ned_m, so no conversion is required.
     896       57413 :         const Vector3p ofs_dist_vec = _ofs_estimate_pos_ned_m - current_position_ned_m;
     897             : 
     898             :         // record distance and bearing to target for reporting/logging
     899       57413 :         if (ofs_dist_vec.xy().is_zero()) {
     900           0 :             clear_dist_and_bearing_to_target();
     901             :         } else {
     902       57413 :             _dist_to_target_m = ofs_dist_vec.xy().length();
     903       57413 :             _bearing_to_target_deg = degrees(ofs_dist_vec.xy().angle());
     904             :         }
     905             :     }
     906       57413 : }
     907             : 
     908             : 
     909             : //==============================================================================
     910             : // Logging
     911             : //==============================================================================
     912             : 
     913             : // Writes a diagnostic onboard log message containing target and vehicle tracking data for Follow mode.
     914             : #if HAL_LOGGING_ENABLED
     915         812 : void AP_Follow::Log_Write_FOLL()
     916             : {
     917             :     // retrieve latest estimated location and velocity
     918         812 :     Location loc_estimate{};
     919         812 :     Vector3f vel_estimate;
     920         812 :     UNUSED_RESULT(get_target_location_and_velocity(loc_estimate, vel_estimate));
     921             : 
     922         812 :     Location target_location;
     923         812 :     UNUSED_RESULT(AP::ahrs().get_location_from_origin_offset_NED(target_location, _target_pos_ned_m));
     924             : 
     925             :     // log the lead target's reported position and vehicle's estimated position
     926             :     // @LoggerMessage: FOLL
     927             :     // @Description: Follow library diagnostic data
     928             :     // @Field: TimeUS: Time since system startup (microseconds)
     929             :     // @Field: Lat: Target latitude (degrees * 1E7)
     930             :     // @Field: Lon: Target longitude (degrees * 1E7)
     931             :     // @Field: Alt: Target absolute altitude (centimeters)
     932             :     // @Field: VelN: Target velocity, North (m/s)
     933             :     // @Field: VelE: Target velocity, East (m/s)
     934             :     // @Field: VelD: Target velocity, Down (m/s)
     935             :     // @Field: LatE: Vehicle estimated latitude (degrees * 1E7)
     936             :     // @Field: LonE: Vehicle estimated longitude (degrees * 1E7)
     937             :     // @Field: AltE: Vehicle estimated altitude (centimeters)
     938             :     // @Field: FrmE: Vehicle estimated altitude Frame
     939        1624 :     AP::logger().WriteStreaming("FOLL",
     940             :                                 "TimeUS,Lat,Lon,Alt,VelN,VelE,VelD,LatE,LonE,AltE,FrmE",  // labels
     941             :                                 "sDUmnnnDUm-",    // units
     942             :                                 "F--B000--B-",    // mults
     943             :                                 "QLLifffLLib",    // fmt
     944             :                                 AP_HAL::micros64(),
     945             :                                 target_location.lat,
     946             :                                 target_location.lng,
     947             :                                 target_location.alt,
     948         812 :                                 (double)_target_vel_ned_ms.x,
     949         812 :                                 (double)_target_vel_ned_ms.y,
     950         812 :                                 (double)_target_vel_ned_ms.z,
     951             :                                 loc_estimate.lat,
     952             :                                 loc_estimate.lng,
     953             :                                 loc_estimate.alt,
     954             :                                 loc_estimate.get_alt_frame()
     955             :                                 );
     956         812 : }
     957             : #endif  // HAL_LOGGING_ENABLED
     958             : 
     959             : 
     960             : //==============================================================================
     961             : // Accessors and Helpers
     962             : //==============================================================================
     963             : 
     964             : // Returns true if the target data we hold is fresh and was supplied by the configured system.
     965             : // This is the gate for update_estimates().  It deliberately excludes _estimate_valid so that
     966             : // update_estimates() can rebuild the estimate after that flag has been cleared.
     967    39489009 : bool AP_Follow::have_target_data(void) const
     968             : {
     969    39489009 :     if (!_enabled) {
     970    39075835 :         return false;
     971             :     }
     972             : 
     973             :     // no target system has been configured
     974      413174 :     if (_sysid == 0) {
     975      215638 :         return false;
     976             :     }
     977             : 
     978             :     // we have not yet accepted an update from the configured system
     979      197536 :     if (_sysid_of_data != _sysid) {
     980       18947 :         return false;
     981             :     }
     982             : 
     983      178588 :     return true;
     984             : }
     985             : 
     986             : // Returns true if a usable estimate of the configured target is available.
     987             : // Every accessor is gated on this, but a true return does not guarantee that they succeed.
     988             : // The location getters can still fail on the AHRS origin lookup or on the FOLL_ALT_TYPE frame
     989             : // conversion, and the target state can change between this call and the accessor call, so
     990             : // callers must check the value every accessor returns.
     991       70523 : bool AP_Follow::have_target(void) const
     992             : {
     993       70523 :     return _estimate_valid && have_target_data();
     994             : }
     995             : 
     996             : //==============================================================================
     997             : // AP_Follow Accessor
     998             : //==============================================================================
     999             : 
    1000             : // Accessor for the AP_Follow singleton instance.
    1001             : namespace AP {
    1002           0 :     AP_Follow &follow()
    1003             :     {
    1004           0 :         return *AP_Follow::get_singleton();
    1005             :     }
    1006             : }
    1007             : 
    1008             : #endif  // AP_FOLLOW_ENABLED

Generated by: LCOV version 1.14