Path: blob/master/libraries/AP_Follow/AP_Follow.cpp
9360 views
/*1This program is free software: you can redistribute it and/or modify2it under the terms of the GNU General Public License as published by3the Free Software Foundation, either version 3 of the License, or4(at your option) any later version.56This program is distributed in the hope that it will be useful,7but WITHOUT ANY WARRANTY; without even the implied warranty of8MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9GNU General Public License for more details.1011You should have received a copy of the GNU General Public License12along with this program. If not, see <http://www.gnu.org/licenses/>.13*/1415//==============================================================================16// AP_Follow Library17// Target Following Logic for ArduPilot Vehicles18//==============================================================================192021//==============================================================================22// Includes23//==============================================================================2425#include "AP_Follow_config.h"2627#if AP_FOLLOW_ENABLED2829#include <AP_HAL/AP_HAL.h>30#include "AP_Follow.h"31#include <ctype.h>32#include <stdio.h>3334#include <AP_AHRS/AP_AHRS.h>35#include <AP_Logger/AP_Logger.h>36#include <GCS_MAVLink/GCS.h>37#include <AP_Vehicle/AP_Vehicle_Type.h>38#include <AP_Scheduler/AP_Scheduler.h>3940extern const AP_HAL::HAL& hal;414243//==============================================================================44// Constants and Definitions45//==============================================================================4647#define AP_FOLLOW_TIMEOUT 3 // position estimate timeout in seconds48#define AP_FOLLOW_SYSID_TIMEOUT_MS 10000 // forget sysid we are following if we have not heard from them in 10 seconds4950#define AP_FOLLOW_OFFSET_TYPE_NED 0 // offsets are in north-east-down frame51#define AP_FOLLOW_OFFSET_TYPE_RELATIVE 1 // offsets are relative to lead vehicle's heading5253#define AP_FOLLOW_POS_P_DEFAULT 0.1f // position error gain default5455#if APM_BUILD_TYPE(APM_BUILD_ArduPlane)56#define AP_FOLLOW_ALT_TYPE_DEFAULT static_cast<float>(Location::AltFrame::ABSOLUTE)57#define AP_FOLLOW_DIST_MAX_DEFAULT 0 // zero = ignored58#else59#define AP_FOLLOW_ALT_TYPE_DEFAULT static_cast<float>(Location::AltFrame::ABOVE_HOME)60#define AP_FOLLOW_DIST_MAX_DEFAULT 10061#endif626364//==============================================================================65// Constructor66//==============================================================================6768AP_Follow *AP_Follow::_singleton;6970// table of user settable parameters71const AP_Param::GroupInfo AP_Follow::var_info[] = {7273// @Param: _ENABLE74// @DisplayName: Follow enable/disable75// @Description: Enabled/disable following a target76// @Values: 0:Disabled,1:Enabled77// @User: Standard78AP_GROUPINFO_FLAGS("_ENABLE", 1, AP_Follow, _enabled, 0, AP_PARAM_FLAG_ENABLE),7980// 2 is reserved for TYPE parameter8182// @Param: _SYSID83// @DisplayName: Follow target's mavlink system id84// @Description: Follow target's mavlink system id85// @Range: 0 25586// @User: Standard87AP_GROUPINFO("_SYSID", 3, AP_Follow, _sysid, 0),8889// 4 is reserved for MARGIN parameter9091// @Param: _DIST_MAX92// @DisplayName: Follow distance maximum93// @Description: Follow distance maximum. If exceeded, the follow estimate will be considered invalid. If zero there is no maximum.94// @Units: m95// @Range: 0 100096// @User: Standard97AP_GROUPINFO("_DIST_MAX", 5, AP_Follow, _dist_max_m, AP_FOLLOW_DIST_MAX_DEFAULT),9899// @Param: _OFS_TYPE100// @DisplayName: Follow offset type101// @Description: Follow offset type102// @Values: 0:North-East-Down, 1:Relative to lead vehicle heading103// @User: Standard104AP_GROUPINFO("_OFS_TYPE", 6, AP_Follow, _offset_type, AP_FOLLOW_OFFSET_TYPE_NED),105106// @Param: _OFS_X107// @DisplayName: Follow offsets in meters north/forward108// @Description: Follow offsets in meters north/forward. If positive, this vehicle fly ahead or north of lead vehicle. Depends on FOLL_OFS_TYPE109// @Range: -100 100110// @Units: m111// @Increment: 1112// @User: Standard113114// @Param: _OFS_Y115// @DisplayName: Follow offsets in meters east/right116// @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_TYPE117// @Range: -100 100118// @Units: m119// @Increment: 1120// @User: Standard121122// @Param: _OFS_Z123// @DisplayName: Follow offsets in meters down124// @Description: Follow offsets in meters down. If positive, this vehicle will fly below the lead vehicle125// @Range: -100 100126// @Units: m127// @Increment: 1128// @User: Standard129AP_GROUPINFO("_OFS", 7, AP_Follow, _offset_m, 0),130131#if !(APM_BUILD_TYPE(APM_BUILD_Rover))132// @Param: _YAW_BEHAVE133// @DisplayName: Follow yaw behaviour134// @Description: Follow yaw behaviour135// @Values: 0:None,1:Face Lead Vehicle,2:Same as Lead vehicle,3:Direction of Flight136// @User: Standard137AP_GROUPINFO("_YAW_BEHAVE", 8, AP_Follow, _yaw_behave, 1),138#endif139140// @Param: _POS_P141// @DisplayName: Follow position error P gain142// @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 controller143// @Range: 0.01 1.00144// @Increment: 0.01145// @User: Standard146AP_SUBGROUPINFO(_p_pos, "_POS_", 9, AP_Follow, AC_P),147148#if !(APM_BUILD_TYPE(APM_BUILD_Rover))149// @Param: _ALT_TYPE150// @DisplayName: Follow altitude type151// @Description: Follow altitude type152// @Values: 0:absolute, 1:relative, 3:terrain153// @User: Standard154AP_GROUPINFO("_ALT_TYPE", 10, AP_Follow, _alt_type, AP_FOLLOW_ALT_TYPE_DEFAULT),155#endif156157// @Param: _OPTIONS158// @DisplayName: Follow options159// @Description: Follow options bitmask160// @Values: 0:None,1: Mount Follows lead vehicle on mode enter161// @User: Standard162AP_GROUPINFO("_OPTIONS", 11, AP_Follow, _options, 0),163164// @Param: _ACCEL_NE165// @DisplayName: Acceleration limit for the horizontal kinematic input shaping166// @Description: Acceleration limit of the horizontal kinematic path generation used to determine how quickly the estimate varies in velocity167// @Range: 0 5168// @Units: m/s/s169// @User: Advanced170AP_GROUPINFO("_ACCEL_NE", 12, AP_Follow, _accel_max_ne_mss, 2.5),171172// @Param: _JERK_NE173// @DisplayName: Jerk limit for the horizontal kinematic input shaping174// @Description: Jerk limit of the horizontal kinematic path generation used to determine how quickly the estimate varies in acceleration175// @Range: 0 20176// @Units: m/s/s/s177// @User: Advanced178AP_GROUPINFO("_JERK_NE", 13, AP_Follow, _jerk_max_ne_msss, 5.0),179180// @Param: _ACCEL_D181// @DisplayName: Acceleration limit for the vertical kinematic input shaping182// @Description: Acceleration limit of the vertical kinematic path generation used to determine how quickly the estimate varies in velocity183// @Range: 0 2.5184// @Units: m/s/s185// @User: Advanced186AP_GROUPINFO("_ACCEL_D", 14, AP_Follow, _accel_max_d_mss, 2.5),187188// @Param: _JERK_D189// @DisplayName: Jerk limit for the vertical kinematic input shaping190// @Description: Jerk limit of the vertical kinematic path generation used to determine how quickly the estimate varies in acceleration191// @Range: 0 5192// @Units: m/s/s/s193// @User: Advanced194AP_GROUPINFO("_JERK_D", 15, AP_Follow, _jerk_max_d_msss, 5.0),195196// @Param: _ACCEL_H197// @DisplayName: Angular acceleration limit for the heading kinematic input shaping198// @Description: Angular acceleration limit of the heading kinematic path generation used to determine how quickly the estimate varies in angular velocity199// @Range: 0 90200// @Units: deg/s/s201// @User: Advanced202AP_GROUPINFO("_ACCEL_H", 16, AP_Follow, _accel_max_h_degss, 90.0),203204// @Param: _JERK_H205// @DisplayName: Angular jerk limit for the heading kinematic input shaping206// @Description: Angular jerk limit of the heading kinematic path generation used to determine how quickly the estimate varies in angular acceleration207// @Range: 0 360208// @Units: deg/s/s/s209// @User: Advanced210AP_GROUPINFO("_JERK_H", 17, AP_Follow, _jerk_max_h_degsss, 360.0),211212// @Param: _TIMEOUT213// @DisplayName: Follow timeout214// @Description: Follow position update from lead - timeout after x seconds215// @User: Standard216// @Units: s217AP_GROUPINFO("_TIMEOUT", 18, AP_Follow, _timeout, AP_FOLLOW_TIMEOUT),218219AP_GROUPEND220};221222// Constructor for AP_Follow. Initializes the position P-controller and sets up parameter defaults.223AP_Follow::AP_Follow() :224_p_pos(AP_FOLLOW_POS_P_DEFAULT)225{226_singleton = this;227AP_Param::setup_object_defaults(this, var_info);228}229230231//==============================================================================232// Target Estimation Update Functions233//==============================================================================234235// Projects and updates the estimated target position, velocity, and heading based on last known data and configured input shaping.236void AP_Follow::update_estimates()237{238WITH_SEMAPHORE(_follow_sem);239240// check for target: if no valid target, invalidate estimate241if (!have_target()) {242clear_dist_and_bearing_to_target();243_estimate_valid = false;244return;245}246247// if sysid changed, reset the estimation state248if (_sysid != _sysid_used) {249_sysid_used = _sysid;250_estimate_valid = false;251}252253const uint32_t now = AP_HAL::millis();254255// calculate time since last location update in seconds256const float dt = (now - _last_location_update_ms) * 0.001f;257258// project target's position and velocity forward using simple kinematics259Vector3f delta_pos_m = _target_vel_ned_ms * dt + _target_accel_ned_mss * 0.5f * sq(dt);260Vector3f delta_vel_ms = _target_accel_ned_mss * dt;261float delta_heading_rad = radians(_target_heading_rate_degs) * dt;262263// calculate time since last estimation update264const float e_dt = (now - _last_estimation_update_ms) * 0.001f;265266const bool valid_kinematic_params = (_accel_max_ne_mss > 0.0f) && (_jerk_max_ne_msss > 0.0f) &&267(_accel_max_d_mss > 0.0f) && (_jerk_max_d_msss > 0.0f) &&268(_accel_max_h_degss > 0.0f) && (_jerk_max_h_degsss > 0.0f);269270if (_estimate_valid && valid_kinematic_params) {271// update X/Y position, velocity, acceleration with shaping272update_pos_vel_accel_xy(_estimate_pos_ned_m.xy(), _estimate_vel_ned_ms.xy(), _estimate_accel_ned_mss.xy(), e_dt, Vector2f(), Vector2f(), Vector2f());273274// update Z axis position, velocity, acceleration without shaping (direct update)275update_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);276277// apply horizontal shaping to refine estimate toward projected target state278shape_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(),279_estimate_pos_ned_m.xy(), _estimate_vel_ned_ms.xy(), _estimate_accel_ned_mss.xy(),2800.0, _accel_max_ne_mss, _jerk_max_ne_msss, e_dt, false);281282// apply angular shaping for heading estimate283shape_angle_vel_accel(radians(_target_heading_deg) + delta_heading_rad, radians(_target_heading_rate_degs), 0.0,284_estimate_heading_rad, _estimate_heading_rate_rads, _estimate_heading_accel_radss,2850.0, 0.0, radians(_accel_max_h_degss),286radians(_jerk_max_h_degsss), e_dt, false);287288// update heading angle separately to maintain proper wrapping [-PI, PI]289postype_t estimate_heading_rad = _estimate_heading_rad;290update_pos_vel_accel(estimate_heading_rad, _estimate_heading_rate_rads, _estimate_heading_accel_radss, e_dt, 0.0, 0.0, 0.0);291_estimate_heading_rad = wrap_PI(float(estimate_heading_rad));292} else {293// no valid estimate yet: initialise from latest target position294_estimate_pos_ned_m = _target_pos_ned_m + delta_pos_m.topostype();295_estimate_vel_ned_ms = _target_vel_ned_ms + delta_vel_ms;296_estimate_accel_ned_mss = _target_accel_ned_mss;297_estimate_heading_rad = radians(_target_heading_deg) + delta_heading_rad;298_estimate_heading_rate_rads = radians(_target_heading_rate_degs);299_estimate_valid = true;300}301302Vector3f offset_m = _offset_m.get();303304// calculate estimated position and velocity with offsets applied305if (offset_m.is_zero() || (_offset_type == AP_FOLLOW_OFFSET_TYPE_NED)) {306// offsets are in NED frame: simple addition307_ofs_estimate_pos_ned_m = _estimate_pos_ned_m + offset_m.topostype();308_ofs_estimate_vel_ned_ms = _estimate_vel_ned_ms;309_ofs_estimate_accel_ned_mss = _estimate_accel_ned_mss;310} else {311// offsets are in FRD frame: rotate by heading312offset_m.xy().rotate(_estimate_heading_rad);313_ofs_estimate_pos_ned_m = _estimate_pos_ned_m + offset_m.topostype();314_ofs_estimate_vel_ned_ms = _estimate_vel_ned_ms;315_ofs_estimate_accel_ned_mss = _estimate_accel_ned_mss;316// with kinematic shaping of heading we can improve our offset velocity and acceleration of the offset317if (valid_kinematic_params) {318Vector3f offset_cross = offset_m.cross(Vector3f{0.0, 0.0, 1.0});319float offset_length_m = offset_m.length();320_ofs_estimate_vel_ned_ms += offset_cross * offset_length_m * _estimate_heading_rate_rads;321_ofs_estimate_accel_ned_mss += offset_cross * offset_length_m * _estimate_heading_accel_radss;322}323}324325// update the distance and bearing to the target326update_dist_and_bearing_to_target();327328_last_estimation_update_ms = now;329330// Check if the target is within the maximum distance331Vector3p current_position_ned_m;332if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {333// no idea where we are; knowing where other things are won't help.334_estimate_valid = false;335return;336}337const Vector3p dist_vec_ned_m = _target_pos_ned_m - current_position_ned_m;338// If _dist_max_m is not positive, we don't check the distance339if (is_positive(_dist_max_m.get()) && (dist_vec_ned_m.length() > _dist_max_m)) {340// target is too far away, mark the estimate invalid341_estimate_valid = false;342}343}344345346//==============================================================================347// Target Information Retrieval Functions348//==============================================================================349350// Retrieves the estimated target position, velocity, and acceleration in the NED frame (relative to origin).351bool AP_Follow::get_target_pos_vel_accel_NED_m(Vector3p &pos_ned_m, Vector3f &vel_ned_ms, Vector3f &accel_ned_mss) const352{353if (!_estimate_valid) {354return false;355}356357pos_ned_m = _estimate_pos_ned_m;358vel_ned_ms = _estimate_vel_ned_ms;359accel_ned_mss = _estimate_accel_ned_mss;360361return true;362}363364// Retrieves the estimated target position, velocity, and acceleration in the NED frame, including configured offsets.365bool AP_Follow::get_ofs_pos_vel_accel_NED_m(Vector3p &pos_ofs_ned_m, Vector3f &vel_ofs_ned_ms, Vector3f &accel_ofs_ned_mss) const366{367if (!_estimate_valid) {368return false;369}370371pos_ofs_ned_m = _ofs_estimate_pos_ned_m;372vel_ofs_ned_ms = _ofs_estimate_vel_ned_ms;373accel_ofs_ned_mss = _ofs_estimate_accel_ned_mss;374375return true;376}377378// Retrieves distance vectors (with and without configured offsets) and the target’s velocity, all in the NED frame.379bool AP_Follow::get_target_dist_and_vel_NED_m(Vector3f &dist_ned, Vector3f &dist_with_offs, Vector3f &vel_ned)380{381WITH_SEMAPHORE(_follow_sem);382383if (!_estimate_valid) {384return false;385}386387Vector3p current_position_ned_m;388if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {389return false;390}391392const Vector3p dist_vec_ned_m = _estimate_pos_ned_m - current_position_ned_m;393const Vector3p ofs_dist_vec = _ofs_estimate_pos_ned_m - current_position_ned_m;394dist_ned = dist_vec_ned_m.tofloat();395dist_with_offs = ofs_dist_vec.tofloat();396vel_ned = _ofs_estimate_vel_ned_ms;397398return true;399}400401// Retrieves the estimated target heading and heading rate in radians.402bool AP_Follow::get_heading_heading_rate_rad(float &heading_rad, float &heading_rate_rads) const403{404if (!_estimate_valid) {405return false;406}407408// return latest heading estimate409heading_rad = _estimate_heading_rad;410heading_rate_rads = _estimate_heading_rate_rads;411return true;412}413414// Retrieves the target's estimated global location and estimated velocity415bool AP_Follow::get_target_location_and_velocity(Location &loc, Vector3f &vel_ned)416{417WITH_SEMAPHORE(_follow_sem);418419if (!_estimate_valid) {420return false;421}422423if (!AP::ahrs().get_location_from_origin_offset_NED(loc, _estimate_pos_ned_m)) {424return false;425}426vel_ned = _estimate_vel_ned_ms;427428// The frame requested by FOLL_ALT_TYPE may not be the frame of location returned by ahrs.429// Make sure we give the caller the frame they have asked for.430return loc.change_alt_frame(_alt_type);431}432433// Retrieves the target's estimated global location including configured offsets, and estimated velocity, for LUA bindings.434bool AP_Follow::get_target_location_and_velocity_ofs(Location &loc, Vector3f &vel_ned)435{436WITH_SEMAPHORE(_follow_sem);437438if (!_estimate_valid) {439return false;440}441if (!AP::ahrs().get_location_from_origin_offset_NED(loc, _ofs_estimate_pos_ned_m)) {442return false;443}444445vel_ned = _ofs_estimate_vel_ned_ms;446return true;447}448449// Retrieves the estimated target heading in degrees (0° = North, 90° = East) for LUA bindings.450bool AP_Follow::get_target_heading_deg(float &heading_deg)451{452WITH_SEMAPHORE(_follow_sem);453454if (!_estimate_valid) {455return false;456}457458// return latest heading estimate459heading_deg = degrees(_estimate_heading_rad);460return true;461}462463// Retrieves the estimated target heading in degrees (0° = North, 90° = East) for LUA bindings.464bool AP_Follow::get_target_heading_rate_degs(float &heading_rate_degs)465{466WITH_SEMAPHORE(_follow_sem);467468if (!_estimate_valid) {469return false;470}471472// return latest heading estimate473heading_rate_degs = degrees(_estimate_heading_rate_rads);474return true;475}476477478//==============================================================================479// MAVLink Message Handling480//==============================================================================481482// Handles incoming MAVLink messages to update the target's position, velocity, and heading.483void AP_Follow::handle_msg(const mavlink_message_t &msg)484{485// Invalidate the estimate if no position update has been received within the timeout period.486// If using automatic sysid tracking, clear the sysid and reset tracking state.487if ((_last_location_update_ms == 0) ||488(AP_HAL::millis() - _last_location_update_ms > AP_FOLLOW_SYSID_TIMEOUT_MS)) {489if (_automatic_sysid) {490_sysid.set(0); // clear target system ID491_sysid_used = 0; // reset used sysid tracking492}493_estimate_valid = false; // mark estimate as invalid494_using_follow_target = false; // reset follow-target usage flag495}496497if (!should_handle_message(msg)) {498// ignore message if filtering rules reject it (e.g., wrong sysid)499return;500}501502// decode MAVLink message503bool updated = false;504505switch (msg.msgid) {506case MAVLINK_MSG_ID_GLOBAL_POSITION_INT: {507// handle standard global position messages508updated = handle_global_position_int_message(msg);509break;510}511case MAVLINK_MSG_ID_FOLLOW_TARGET: {512// handle follow-target specific messages513updated = handle_follow_target_message(msg);514break;515}516}517518if (updated) {519// Check if estimate needs reset based on position and velocity errors520if (estimate_error_too_large()) {521_estimate_valid = false;522}523524#if HAL_LOGGING_ENABLED525// log current follow diagnostic data526Log_Write_FOLL();527#endif528}529}530531// Returns true if the incoming MAVLink message should be processed for target updates.532bool AP_Follow::should_handle_message(const mavlink_message_t &msg) const533{534// exit immediately if not enabled535if (!_enabled) {536return false;537}538539// skip our own messages540if (msg.sysid == mavlink_system.sysid) {541return false;542}543544// skip message if not from our target545if (_sysid != 0 && msg.sysid != _sysid) {546return false;547}548549return true;550}551552// Checks whether the current estimate should be reset based on position and velocity errors.553bool AP_Follow::estimate_error_too_large() const554{555const float timeout_sec = _timeout;556557// Calculate position thresholds based on maximum acceleration then deceleration for the timeout duration558const float pos_thresh_horiz_m = _accel_max_ne_mss.get() * sq(timeout_sec * 0.5);559const float pos_thresh_vert_m = _accel_max_d_mss.get() * sq(timeout_sec * 0.5);560561// Calculate velocity thresholds using the new helper function562const float vel_thresh_horiz_ms = calc_max_velocity_change(_accel_max_ne_mss.get(), _jerk_max_ne_msss.get(), timeout_sec);563const float vel_thresh_vert_ms = calc_max_velocity_change(_accel_max_d_mss.get(), _jerk_max_d_msss.get(), timeout_sec);564565// Calculate current position and velocity errors566const Vector3f pos_error = _estimate_pos_ned_m.tofloat() - _target_pos_ned_m.tofloat();567const Vector3f vel_error = _estimate_vel_ned_ms - _target_vel_ned_ms;568569const Vector2f pos_error_xy = pos_error.xy();570const float pos_error_z = pos_error.z;571const Vector2f vel_error_xy = vel_error.xy();572const float vel_error_z = vel_error.z;573574// Check horizontal and vertical separately575const bool pos_horiz_bad = pos_error_xy.length() > pos_thresh_horiz_m;576const bool vel_horiz_bad = vel_error_xy.length() > vel_thresh_horiz_ms;577const bool pos_vert_bad = fabsf(pos_error_z) > pos_thresh_vert_m;578const bool vel_vert_bad = fabsf(vel_error_z) > vel_thresh_vert_ms;579580return pos_horiz_bad || vel_horiz_bad || pos_vert_bad || vel_vert_bad;581}582583// Calculates max velocity change under trapezoidal or triangular acceleration profile (jerk-limited).584float AP_Follow::calc_max_velocity_change(float accel_max, float jerk_max, float timeout_sec) const585{586const float t_jerk = accel_max / jerk_max;587const float t_total_jerk = 2.0f * t_jerk;588589if (timeout_sec >= t_total_jerk) {590// time to ramp up, constant accel phase, and ramp down591const float t_const = timeout_sec - t_total_jerk;592const float delta_v_jerk = 0.5f * accel_max * t_jerk;593const float delta_v_const = accel_max * t_const;594return 2.0f * delta_v_jerk + delta_v_const;595} else {596// timeout too short: pure triangle profile597const float t_half = timeout_sec * 0.5f;598return 0.5f * jerk_max * sq(t_half);599}600}601602// Decodes a GLOBAL_POSITION_INT MAVLink message to update the target’s position, velocity, and heading.603bool AP_Follow::handle_global_position_int_message(const mavlink_message_t &msg)604{605// decode GLOBAL_POSITION_INT message into packet struct606mavlink_global_position_int_t packet;607mavlink_msg_global_position_int_decode(&msg, &packet);608609// ignore message if latitude and longitude are exactly zero (invalid GPS fix)610if ((packet.lat == 0 && packet.lon == 0)) {611return false;612}613614if (_using_follow_target) {615// if we are using follow_target, ignore global_position_int messages616return false;617}618619Location target_location;620target_location.lat = packet.lat;621target_location.lng = packet.lon;622623switch((Location::AltFrame)_alt_type) {624case Location::AltFrame::ABSOLUTE:625target_location.set_alt_cm(packet.alt * 0.1, Location::AltFrame::ABSOLUTE);626break;627case Location::AltFrame::ABOVE_HOME:628target_location.set_alt_cm(packet.relative_alt * 0.1, Location::AltFrame::ABOVE_HOME);629break;630#if APM_BUILD_TYPE(APM_BUILD_ArduPlane) || APM_BUILD_TYPE(APM_BUILD_ArduCopter)631case Location::AltFrame::ABOVE_TERRAIN:632/// Altitude comes in as AMSL633target_location.set_alt_cm(packet.alt * 0.1, Location::AltFrame::ABSOLUTE);634// convert the incoming altitude to terrain altitude, but fail if there is no terrain data available635if (!target_location.change_alt_frame(Location::AltFrame::ABOVE_TERRAIN)) {636return false;637};638break;639#endif640default:641// don't process the packet if the _alt_type is invalid642return false;643}644645// convert global location to local NED frame position646Vector3p target_pos_neu_m;647if (!target_location.get_vector_from_origin_NEU_m(target_pos_neu_m)) {648return false;649}650651if (packet.hdg <= 36000) {652// valid heading field available (in centi-degrees)653_target_heading_deg = packet.hdg * 0.01f;654} else if (_offset_type == AP_FOLLOW_OFFSET_TYPE_RELATIVE) {655// heading missing but relative offset mode requires heading -> reject656return false;657} else {658// no heading available: set heading rate to zero659_target_heading_rate_degs = 0.0f;660}661662_target_pos_ned_m.xy() = target_pos_neu_m.xy();663_target_pos_ned_m.z = -target_pos_neu_m.z;664665// decode target velocity components (cm/s converted to m/s)666_target_vel_ned_ms.x = packet.vx * 0.01f; // velocity north667_target_vel_ned_ms.y = packet.vy * 0.01f; // velocity east668_target_vel_ned_ms.z = packet.vz * 0.01f; // velocity down669670// target acceleration not available in GLOBAL_POSITION_INT671_target_accel_ned_mss.zero();672673// apply jitter-corrected timestamp to this update674_last_location_update_ms = _jitter.correct_offboard_timestamp_msec(packet.time_boot_ms, AP_HAL::millis());675676// if sysid not yet set, adopt sender’s sysid and enable automatic sysid tracking677if (_sysid == 0) {678_sysid.set(msg.sysid);679_sysid_used = 0;680_estimate_valid = false;681_automatic_sysid = true;682}683684return true;685}686687// Decodes a FOLLOW_TARGET MAVLink message to update the target’s position, velocity, acceleration, and orientation.688bool AP_Follow::handle_follow_target_message(const mavlink_message_t &msg)689{690// decode FOLLOW_TARGET message into packet struct691mavlink_follow_target_t packet;692mavlink_msg_follow_target_decode(&msg, &packet);693694// ignore message if latitude and longitude are exactly zero (invalid GPS fix)695if ((packet.lat == 0 && packet.lon == 0)) {696return false;697}698699// require that at least position is estimated (bit 0 of est_capabilities)700if ((packet.est_capabilities & (1<<0)) == 0) {701return false;702}703704// build Location object from latitude, longitude, and altitude (alt in meters)705const Location target_location {706packet.lat,707packet.lon,708int32_t(packet.alt * 100), // convert meters to centimeters709Location::AltFrame::ABSOLUTE710};711712// convert global location to local NED frame position713Vector3p target_pos_neu_m;714if (!target_location.get_vector_from_origin_NEU_m(target_pos_neu_m)) {715return false;716}717718// adjust Z coordinate to NED frame (NEU altitude -> NED)719Location origin;720if (!AP::ahrs().get_origin(origin)) {721return false;722}723724// decode attitude if available (bit 3 of est_capabilities)725if (packet.est_capabilities & (1 << 3)) {726// reconstruct quaternion from packet727Quaternion q{packet.attitude_q[0], packet.attitude_q[1], packet.attitude_q[2], packet.attitude_q[3]};728float roll, pitch, yaw;729q.to_euler(roll, pitch, yaw);730731// store heading in degrees, wrapped 0–360732_target_heading_deg = wrap_360(degrees(yaw));733734// transform body rates (roll, pitch, yaw) to earth frame rates735Matrix3f R;736q.rotation_matrix(R);737Vector3f omega_body = Vector3f{packet.rates[0], packet.rates[1], packet.rates[2]};738Vector3f omega_world = R * omega_body; // rotate rates into earth frame739740// store heading rate (yaw rate in world frame) in degrees/sec741_target_heading_rate_degs = degrees(omega_world.z);742} else if (_offset_type == AP_FOLLOW_OFFSET_TYPE_RELATIVE) {743// if attitude unavailable and using relative frame, cannot compute — abort744return false;745} else {746// otherwise, default heading rate to zero747_target_heading_rate_degs = 0.0f;748}749750_target_pos_ned_m.xy() = target_pos_neu_m.xy();751_target_pos_ned_m.z = -packet.alt + origin.alt * 0.01;752753// decode velocity if available (bit 1 of est_capabilities)754if (packet.est_capabilities & (1<<1)) {755_target_vel_ned_ms.x = packet.vel[0]; // velocity north756_target_vel_ned_ms.y = packet.vel[1]; // velocity east757_target_vel_ned_ms.z = packet.vel[2]; // velocity down758} else {759_target_vel_ned_ms.zero();760}761762// decode acceleration if available (bit 2 of est_capabilities)763if (packet.est_capabilities & (1 << 2)) {764_target_accel_ned_mss.x = packet.acc[0]; // acceleration north765_target_accel_ned_mss.y = packet.acc[1]; // acceleration east766_target_accel_ned_mss.z = packet.acc[2]; // acceleration down767} else {768_target_accel_ned_mss.zero();769}770771// apply jitter-corrected timestamp to this update772_last_location_update_ms = _jitter.correct_offboard_timestamp_msec(packet.timestamp, AP_HAL::millis());773774// if sysid not yet assigned, adopt sender's sysid and enable automatic sysid tracking775if (_sysid == 0) {776_sysid.set(msg.sysid);777_automatic_sysid = true;778}779780// we are using follow_target: set sysid to sender's sysid781_using_follow_target = true;782783return true;784}785786787//==============================================================================788// Offset Initialization and Adjustment Functions789//==============================================================================790791// Initializes the positional offsets from the target vehicle if not already set.792void AP_Follow::init_offsets_if_required()793{794// return immediately if offsets have already been set795if (!_offset_m.get().is_zero()) {796return;797}798_offsets_were_zero = true;799800if (!_estimate_valid) {801return;802}803804// Check if the target is within the maximum distance805Vector3p current_position_ned_m;806if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {807return;808}809const Vector3f dist_vec_ned_m = (_target_pos_ned_m - current_position_ned_m).tofloat();810811if ((_offset_type == AP_FOLLOW_OFFSET_TYPE_RELATIVE)) {812// rotate offset into vehicle-relative frame based on heading813_offset_m.set(rotate_vector(-dist_vec_ned_m, -degrees(_estimate_heading_rad)));814GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Relative follow offset loaded");815} else {816// initialize offset in NED frame (no heading rotation)817_offset_m.set(-dist_vec_ned_m);818819// ensure offset type is set to NED frame if initialized this way820_offset_type.set(AP_FOLLOW_OFFSET_TYPE_NED);821GCS_SEND_TEXT(MAV_SEVERITY_INFO, "N-E-D follow offset loaded");822}823}824825// Rotates a 3D vector clockwise by the specified angle (degrees).826Vector3f AP_Follow::rotate_vector(const Vector3f &vec, float angle_deg) const827{828// rotate roll, pitch input from north facing to vehicle's perspective829Vector3f ret = vec;830ret.xy().rotate(radians(angle_deg));831832return ret;833}834835// Resets follow mode offsets to zero if they were automatically initialized.836void AP_Follow::clear_offsets_if_required()837{838if (_offsets_were_zero) {839_offset_m.set(Vector3f());840_offsets_were_zero = false;841}842}843844845//==============================================================================846// Distance and Bearing Management847//==============================================================================848849// Resets the recorded distance and bearing to the target to zero.850void AP_Follow::clear_dist_and_bearing_to_target()851{852_dist_to_target_m = 0.0f;853_bearing_to_target_deg = 0.0f;854}855856// Updates the recorded distance and bearing to the target to zero.857void AP_Follow::update_dist_and_bearing_to_target()858{859Vector3p current_position_ned_m;860if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {861// if unable to retrieve local position, clear distance/bearing info862clear_dist_and_bearing_to_target();863} else {864// convert vehicle position to NED meters (NEU -> NED and cm -> m)865current_position_ned_m.z = -current_position_ned_m.z; // NEU to NED866current_position_ned_m *= 0.01; // convert cm to m867868// calculate distance vectors to target, both with and without offsets869const Vector3p ofs_dist_vec = _ofs_estimate_pos_ned_m - current_position_ned_m;870871// record distance and bearing to target for reporting/logging872if (ofs_dist_vec.xy().is_zero()) {873clear_dist_and_bearing_to_target();874} else {875_dist_to_target_m = ofs_dist_vec.xy().length();876_bearing_to_target_deg = degrees(ofs_dist_vec.xy().angle());877}878}879}880881882//==============================================================================883// Logging884//==============================================================================885886// Writes a diagnostic onboard log message containing target and vehicle tracking data for Follow mode.887#if HAL_LOGGING_ENABLED888void AP_Follow::Log_Write_FOLL()889{890// retrieve latest estimated location and velocity891Location loc_estimate{};892Vector3f vel_estimate;893UNUSED_RESULT(get_target_location_and_velocity(loc_estimate, vel_estimate));894895Location target_location;896UNUSED_RESULT(AP::ahrs().get_location_from_origin_offset_NED(target_location, _target_pos_ned_m));897898// log the lead target's reported position and vehicle's estimated position899// @LoggerMessage: FOLL900// @Description: Follow library diagnostic data901// @Field: TimeUS: Time since system startup (microseconds)902// @Field: Lat: Target latitude (degrees * 1E7)903// @Field: Lon: Target longitude (degrees * 1E7)904// @Field: Alt: Target absolute altitude (centimeters)905// @Field: VelN: Target velocity, North (m/s)906// @Field: VelE: Target velocity, East (m/s)907// @Field: VelD: Target velocity, Down (m/s)908// @Field: LatE: Vehicle estimated latitude (degrees * 1E7)909// @Field: LonE: Vehicle estimated longitude (degrees * 1E7)910// @Field: AltE: Vehicle estimated altitude (centimeters)911// @Field: FrmE: Vehicle estimated altitude Frame912AP::logger().WriteStreaming("FOLL",913"TimeUS,Lat,Lon,Alt,VelN,VelE,VelD,LatE,LonE,AltE,FrmE", // labels914"sDUmnnnDUm-", // units915"F--B000--B-", // mults916"QLLifffLLib", // fmt917AP_HAL::micros64(),918target_location.lat,919target_location.lng,920target_location.alt,921(double)_target_vel_ned_ms.x,922(double)_target_vel_ned_ms.y,923(double)_target_vel_ned_ms.z,924loc_estimate.lat,925loc_estimate.lng,926loc_estimate.alt,927loc_estimate.get_alt_frame()928);929}930#endif // HAL_LOGGING_ENABLED931932933//==============================================================================934// Accessors and Helpers935//==============================================================================936937// Returns true if following is enabled and a recent target update has been received.938bool AP_Follow::have_target(void) const939{940if (!_enabled) {941return false;942}943944// check for timeout945if ((_last_location_update_ms == 0) || ((AP_HAL::millis() - _last_location_update_ms) > (uint32_t)(_timeout * 1000.0f))) {946return false;947}948return true;949}950951//==============================================================================952// AP_Follow Accessor953//==============================================================================954955// Accessor for the AP_Follow singleton instance.956namespace AP {957AP_Follow &follow()958{959return *AP_Follow::get_singleton();960}961}962963#endif // AP_FOLLOW_ENABLED964965966