Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AP_Follow/AP_Follow.cpp
9360 views
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_SYSID_TIMEOUT_MS 10000 // forget sysid we are following if we have not heard from them 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
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 5
169
// @Units: m/s/s
170
// @User: Advanced
171
AP_GROUPINFO("_ACCEL_NE", 12, AP_Follow, _accel_max_ne_mss, 2.5),
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, 5.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 2.5
185
// @Units: m/s/s
186
// @User: Advanced
187
AP_GROUPINFO("_ACCEL_D", 14, AP_Follow, _accel_max_d_mss, 2.5),
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 5
193
// @Units: m/s/s/s
194
// @User: Advanced
195
AP_GROUPINFO("_JERK_D", 15, AP_Follow, _jerk_max_d_msss, 5.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 90
201
// @Units: deg/s/s
202
// @User: Advanced
203
AP_GROUPINFO("_ACCEL_H", 16, AP_Follow, _accel_max_h_degss, 90.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 360
209
// @Units: deg/s/s/s
210
// @User: Advanced
211
AP_GROUPINFO("_JERK_H", 17, AP_Follow, _jerk_max_h_degsss, 360.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
AP_Follow::AP_Follow() :
225
_p_pos(AP_FOLLOW_POS_P_DEFAULT)
226
{
227
_singleton = this;
228
AP_Param::setup_object_defaults(this, var_info);
229
}
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
void AP_Follow::update_estimates()
238
{
239
WITH_SEMAPHORE(_follow_sem);
240
241
// check for target: if no valid target, invalidate estimate
242
if (!have_target()) {
243
clear_dist_and_bearing_to_target();
244
_estimate_valid = false;
245
return;
246
}
247
248
// if sysid changed, reset the estimation state
249
if (_sysid != _sysid_used) {
250
_sysid_used = _sysid;
251
_estimate_valid = false;
252
}
253
254
const uint32_t now = AP_HAL::millis();
255
256
// calculate time since last location update in seconds
257
const float dt = (now - _last_location_update_ms) * 0.001f;
258
259
// project target's position and velocity forward using simple kinematics
260
Vector3f delta_pos_m = _target_vel_ned_ms * dt + _target_accel_ned_mss * 0.5f * sq(dt);
261
Vector3f delta_vel_ms = _target_accel_ned_mss * dt;
262
float delta_heading_rad = radians(_target_heading_rate_degs) * dt;
263
264
// calculate time since last estimation update
265
const float e_dt = (now - _last_estimation_update_ms) * 0.001f;
266
267
const bool valid_kinematic_params = (_accel_max_ne_mss > 0.0f) && (_jerk_max_ne_msss > 0.0f) &&
268
(_accel_max_d_mss > 0.0f) && (_jerk_max_d_msss > 0.0f) &&
269
(_accel_max_h_degss > 0.0f) && (_jerk_max_h_degsss > 0.0f);
270
271
if (_estimate_valid && valid_kinematic_params) {
272
// update X/Y position, velocity, acceleration with shaping
273
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());
274
275
// update Z axis position, velocity, acceleration without shaping (direct update)
276
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);
277
278
// apply horizontal shaping to refine estimate toward projected target state
279
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(),
280
_estimate_pos_ned_m.xy(), _estimate_vel_ned_ms.xy(), _estimate_accel_ned_mss.xy(),
281
0.0, _accel_max_ne_mss, _jerk_max_ne_msss, e_dt, false);
282
283
// apply angular shaping for heading estimate
284
shape_angle_vel_accel(radians(_target_heading_deg) + delta_heading_rad, radians(_target_heading_rate_degs), 0.0,
285
_estimate_heading_rad, _estimate_heading_rate_rads, _estimate_heading_accel_radss,
286
0.0, 0.0, radians(_accel_max_h_degss),
287
radians(_jerk_max_h_degsss), e_dt, false);
288
289
// update heading angle separately to maintain proper wrapping [-PI, PI]
290
postype_t estimate_heading_rad = _estimate_heading_rad;
291
update_pos_vel_accel(estimate_heading_rad, _estimate_heading_rate_rads, _estimate_heading_accel_radss, e_dt, 0.0, 0.0, 0.0);
292
_estimate_heading_rad = wrap_PI(float(estimate_heading_rad));
293
} else {
294
// no valid estimate yet: initialise from latest target position
295
_estimate_pos_ned_m = _target_pos_ned_m + delta_pos_m.topostype();
296
_estimate_vel_ned_ms = _target_vel_ned_ms + delta_vel_ms;
297
_estimate_accel_ned_mss = _target_accel_ned_mss;
298
_estimate_heading_rad = radians(_target_heading_deg) + delta_heading_rad;
299
_estimate_heading_rate_rads = radians(_target_heading_rate_degs);
300
_estimate_valid = true;
301
}
302
303
Vector3f offset_m = _offset_m.get();
304
305
// calculate estimated position and velocity with offsets applied
306
if (offset_m.is_zero() || (_offset_type == AP_FOLLOW_OFFSET_TYPE_NED)) {
307
// offsets are in NED frame: simple addition
308
_ofs_estimate_pos_ned_m = _estimate_pos_ned_m + offset_m.topostype();
309
_ofs_estimate_vel_ned_ms = _estimate_vel_ned_ms;
310
_ofs_estimate_accel_ned_mss = _estimate_accel_ned_mss;
311
} else {
312
// offsets are in FRD frame: rotate by heading
313
offset_m.xy().rotate(_estimate_heading_rad);
314
_ofs_estimate_pos_ned_m = _estimate_pos_ned_m + offset_m.topostype();
315
_ofs_estimate_vel_ned_ms = _estimate_vel_ned_ms;
316
_ofs_estimate_accel_ned_mss = _estimate_accel_ned_mss;
317
// with kinematic shaping of heading we can improve our offset velocity and acceleration of the offset
318
if (valid_kinematic_params) {
319
Vector3f offset_cross = offset_m.cross(Vector3f{0.0, 0.0, 1.0});
320
float offset_length_m = offset_m.length();
321
_ofs_estimate_vel_ned_ms += offset_cross * offset_length_m * _estimate_heading_rate_rads;
322
_ofs_estimate_accel_ned_mss += offset_cross * offset_length_m * _estimate_heading_accel_radss;
323
}
324
}
325
326
// update the distance and bearing to the target
327
update_dist_and_bearing_to_target();
328
329
_last_estimation_update_ms = now;
330
331
// Check if the target is within the maximum distance
332
Vector3p current_position_ned_m;
333
if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {
334
// no idea where we are; knowing where other things are won't help.
335
_estimate_valid = false;
336
return;
337
}
338
const Vector3p dist_vec_ned_m = _target_pos_ned_m - current_position_ned_m;
339
// If _dist_max_m is not positive, we don't check the distance
340
if (is_positive(_dist_max_m.get()) && (dist_vec_ned_m.length() > _dist_max_m)) {
341
// target is too far away, mark the estimate invalid
342
_estimate_valid = false;
343
}
344
}
345
346
347
//==============================================================================
348
// Target Information Retrieval Functions
349
//==============================================================================
350
351
// Retrieves the estimated target position, velocity, and acceleration in the NED frame (relative to origin).
352
bool AP_Follow::get_target_pos_vel_accel_NED_m(Vector3p &pos_ned_m, Vector3f &vel_ned_ms, Vector3f &accel_ned_mss) const
353
{
354
if (!_estimate_valid) {
355
return false;
356
}
357
358
pos_ned_m = _estimate_pos_ned_m;
359
vel_ned_ms = _estimate_vel_ned_ms;
360
accel_ned_mss = _estimate_accel_ned_mss;
361
362
return true;
363
}
364
365
// Retrieves the estimated target position, velocity, and acceleration in the NED frame, including configured offsets.
366
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
367
{
368
if (!_estimate_valid) {
369
return false;
370
}
371
372
pos_ofs_ned_m = _ofs_estimate_pos_ned_m;
373
vel_ofs_ned_ms = _ofs_estimate_vel_ned_ms;
374
accel_ofs_ned_mss = _ofs_estimate_accel_ned_mss;
375
376
return true;
377
}
378
379
// Retrieves distance vectors (with and without configured offsets) and the target’s velocity, all in the NED frame.
380
bool AP_Follow::get_target_dist_and_vel_NED_m(Vector3f &dist_ned, Vector3f &dist_with_offs, Vector3f &vel_ned)
381
{
382
WITH_SEMAPHORE(_follow_sem);
383
384
if (!_estimate_valid) {
385
return false;
386
}
387
388
Vector3p current_position_ned_m;
389
if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {
390
return false;
391
}
392
393
const Vector3p dist_vec_ned_m = _estimate_pos_ned_m - current_position_ned_m;
394
const Vector3p ofs_dist_vec = _ofs_estimate_pos_ned_m - current_position_ned_m;
395
dist_ned = dist_vec_ned_m.tofloat();
396
dist_with_offs = ofs_dist_vec.tofloat();
397
vel_ned = _ofs_estimate_vel_ned_ms;
398
399
return true;
400
}
401
402
// Retrieves the estimated target heading and heading rate in radians.
403
bool AP_Follow::get_heading_heading_rate_rad(float &heading_rad, float &heading_rate_rads) const
404
{
405
if (!_estimate_valid) {
406
return false;
407
}
408
409
// return latest heading estimate
410
heading_rad = _estimate_heading_rad;
411
heading_rate_rads = _estimate_heading_rate_rads;
412
return true;
413
}
414
415
// Retrieves the target's estimated global location and estimated velocity
416
bool AP_Follow::get_target_location_and_velocity(Location &loc, Vector3f &vel_ned)
417
{
418
WITH_SEMAPHORE(_follow_sem);
419
420
if (!_estimate_valid) {
421
return false;
422
}
423
424
if (!AP::ahrs().get_location_from_origin_offset_NED(loc, _estimate_pos_ned_m)) {
425
return false;
426
}
427
vel_ned = _estimate_vel_ned_ms;
428
429
// The frame requested by FOLL_ALT_TYPE may not be the frame of location returned by ahrs.
430
// Make sure we give the caller the frame they have asked for.
431
return loc.change_alt_frame(_alt_type);
432
}
433
434
// Retrieves the target's estimated global location including configured offsets, and estimated velocity, for LUA bindings.
435
bool AP_Follow::get_target_location_and_velocity_ofs(Location &loc, Vector3f &vel_ned)
436
{
437
WITH_SEMAPHORE(_follow_sem);
438
439
if (!_estimate_valid) {
440
return false;
441
}
442
if (!AP::ahrs().get_location_from_origin_offset_NED(loc, _ofs_estimate_pos_ned_m)) {
443
return false;
444
}
445
446
vel_ned = _ofs_estimate_vel_ned_ms;
447
return true;
448
}
449
450
// Retrieves the estimated target heading in degrees (0° = North, 90° = East) for LUA bindings.
451
bool AP_Follow::get_target_heading_deg(float &heading_deg)
452
{
453
WITH_SEMAPHORE(_follow_sem);
454
455
if (!_estimate_valid) {
456
return false;
457
}
458
459
// return latest heading estimate
460
heading_deg = degrees(_estimate_heading_rad);
461
return true;
462
}
463
464
// Retrieves the estimated target heading in degrees (0° = North, 90° = East) for LUA bindings.
465
bool AP_Follow::get_target_heading_rate_degs(float &heading_rate_degs)
466
{
467
WITH_SEMAPHORE(_follow_sem);
468
469
if (!_estimate_valid) {
470
return false;
471
}
472
473
// return latest heading estimate
474
heading_rate_degs = degrees(_estimate_heading_rate_rads);
475
return true;
476
}
477
478
479
//==============================================================================
480
// MAVLink Message Handling
481
//==============================================================================
482
483
// Handles incoming MAVLink messages to update the target's position, velocity, and heading.
484
void AP_Follow::handle_msg(const mavlink_message_t &msg)
485
{
486
// Invalidate the estimate if no position update has been received within the timeout period.
487
// If using automatic sysid tracking, clear the sysid and reset tracking state.
488
if ((_last_location_update_ms == 0) ||
489
(AP_HAL::millis() - _last_location_update_ms > AP_FOLLOW_SYSID_TIMEOUT_MS)) {
490
if (_automatic_sysid) {
491
_sysid.set(0); // clear target system ID
492
_sysid_used = 0; // reset used sysid tracking
493
}
494
_estimate_valid = false; // mark estimate as invalid
495
_using_follow_target = false; // reset follow-target usage flag
496
}
497
498
if (!should_handle_message(msg)) {
499
// ignore message if filtering rules reject it (e.g., wrong sysid)
500
return;
501
}
502
503
// decode MAVLink message
504
bool updated = false;
505
506
switch (msg.msgid) {
507
case MAVLINK_MSG_ID_GLOBAL_POSITION_INT: {
508
// handle standard global position messages
509
updated = handle_global_position_int_message(msg);
510
break;
511
}
512
case MAVLINK_MSG_ID_FOLLOW_TARGET: {
513
// handle follow-target specific messages
514
updated = handle_follow_target_message(msg);
515
break;
516
}
517
}
518
519
if (updated) {
520
// Check if estimate needs reset based on position and velocity errors
521
if (estimate_error_too_large()) {
522
_estimate_valid = false;
523
}
524
525
#if HAL_LOGGING_ENABLED
526
// log current follow diagnostic data
527
Log_Write_FOLL();
528
#endif
529
}
530
}
531
532
// Returns true if the incoming MAVLink message should be processed for target updates.
533
bool AP_Follow::should_handle_message(const mavlink_message_t &msg) const
534
{
535
// exit immediately if not enabled
536
if (!_enabled) {
537
return false;
538
}
539
540
// skip our own messages
541
if (msg.sysid == mavlink_system.sysid) {
542
return false;
543
}
544
545
// skip message if not from our target
546
if (_sysid != 0 && msg.sysid != _sysid) {
547
return false;
548
}
549
550
return true;
551
}
552
553
// Checks whether the current estimate should be reset based on position and velocity errors.
554
bool AP_Follow::estimate_error_too_large() const
555
{
556
const float timeout_sec = _timeout;
557
558
// Calculate position thresholds based on maximum acceleration then deceleration for the timeout duration
559
const float pos_thresh_horiz_m = _accel_max_ne_mss.get() * sq(timeout_sec * 0.5);
560
const float pos_thresh_vert_m = _accel_max_d_mss.get() * sq(timeout_sec * 0.5);
561
562
// Calculate velocity thresholds using the new helper function
563
const float vel_thresh_horiz_ms = calc_max_velocity_change(_accel_max_ne_mss.get(), _jerk_max_ne_msss.get(), timeout_sec);
564
const float vel_thresh_vert_ms = calc_max_velocity_change(_accel_max_d_mss.get(), _jerk_max_d_msss.get(), timeout_sec);
565
566
// Calculate current position and velocity errors
567
const Vector3f pos_error = _estimate_pos_ned_m.tofloat() - _target_pos_ned_m.tofloat();
568
const Vector3f vel_error = _estimate_vel_ned_ms - _target_vel_ned_ms;
569
570
const Vector2f pos_error_xy = pos_error.xy();
571
const float pos_error_z = pos_error.z;
572
const Vector2f vel_error_xy = vel_error.xy();
573
const float vel_error_z = vel_error.z;
574
575
// Check horizontal and vertical separately
576
const bool pos_horiz_bad = pos_error_xy.length() > pos_thresh_horiz_m;
577
const bool vel_horiz_bad = vel_error_xy.length() > vel_thresh_horiz_ms;
578
const bool pos_vert_bad = fabsf(pos_error_z) > pos_thresh_vert_m;
579
const bool vel_vert_bad = fabsf(vel_error_z) > vel_thresh_vert_ms;
580
581
return pos_horiz_bad || vel_horiz_bad || pos_vert_bad || vel_vert_bad;
582
}
583
584
// Calculates max velocity change under trapezoidal or triangular acceleration profile (jerk-limited).
585
float AP_Follow::calc_max_velocity_change(float accel_max, float jerk_max, float timeout_sec) const
586
{
587
const float t_jerk = accel_max / jerk_max;
588
const float t_total_jerk = 2.0f * t_jerk;
589
590
if (timeout_sec >= t_total_jerk) {
591
// time to ramp up, constant accel phase, and ramp down
592
const float t_const = timeout_sec - t_total_jerk;
593
const float delta_v_jerk = 0.5f * accel_max * t_jerk;
594
const float delta_v_const = accel_max * t_const;
595
return 2.0f * delta_v_jerk + delta_v_const;
596
} else {
597
// timeout too short: pure triangle profile
598
const float t_half = timeout_sec * 0.5f;
599
return 0.5f * jerk_max * sq(t_half);
600
}
601
}
602
603
// Decodes a GLOBAL_POSITION_INT MAVLink message to update the target’s position, velocity, and heading.
604
bool AP_Follow::handle_global_position_int_message(const mavlink_message_t &msg)
605
{
606
// decode GLOBAL_POSITION_INT message into packet struct
607
mavlink_global_position_int_t packet;
608
mavlink_msg_global_position_int_decode(&msg, &packet);
609
610
// ignore message if latitude and longitude are exactly zero (invalid GPS fix)
611
if ((packet.lat == 0 && packet.lon == 0)) {
612
return false;
613
}
614
615
if (_using_follow_target) {
616
// if we are using follow_target, ignore global_position_int messages
617
return false;
618
}
619
620
Location target_location;
621
target_location.lat = packet.lat;
622
target_location.lng = packet.lon;
623
624
switch((Location::AltFrame)_alt_type) {
625
case Location::AltFrame::ABSOLUTE:
626
target_location.set_alt_cm(packet.alt * 0.1, Location::AltFrame::ABSOLUTE);
627
break;
628
case Location::AltFrame::ABOVE_HOME:
629
target_location.set_alt_cm(packet.relative_alt * 0.1, Location::AltFrame::ABOVE_HOME);
630
break;
631
#if APM_BUILD_TYPE(APM_BUILD_ArduPlane) || APM_BUILD_TYPE(APM_BUILD_ArduCopter)
632
case Location::AltFrame::ABOVE_TERRAIN:
633
/// Altitude comes in as AMSL
634
target_location.set_alt_cm(packet.alt * 0.1, Location::AltFrame::ABSOLUTE);
635
// convert the incoming altitude to terrain altitude, but fail if there is no terrain data available
636
if (!target_location.change_alt_frame(Location::AltFrame::ABOVE_TERRAIN)) {
637
return false;
638
};
639
break;
640
#endif
641
default:
642
// don't process the packet if the _alt_type is invalid
643
return false;
644
}
645
646
// convert global location to local NED frame position
647
Vector3p target_pos_neu_m;
648
if (!target_location.get_vector_from_origin_NEU_m(target_pos_neu_m)) {
649
return false;
650
}
651
652
if (packet.hdg <= 36000) {
653
// valid heading field available (in centi-degrees)
654
_target_heading_deg = packet.hdg * 0.01f;
655
} else if (_offset_type == AP_FOLLOW_OFFSET_TYPE_RELATIVE) {
656
// heading missing but relative offset mode requires heading -> reject
657
return false;
658
} else {
659
// no heading available: set heading rate to zero
660
_target_heading_rate_degs = 0.0f;
661
}
662
663
_target_pos_ned_m.xy() = target_pos_neu_m.xy();
664
_target_pos_ned_m.z = -target_pos_neu_m.z;
665
666
// decode target velocity components (cm/s converted to m/s)
667
_target_vel_ned_ms.x = packet.vx * 0.01f; // velocity north
668
_target_vel_ned_ms.y = packet.vy * 0.01f; // velocity east
669
_target_vel_ned_ms.z = packet.vz * 0.01f; // velocity down
670
671
// target acceleration not available in GLOBAL_POSITION_INT
672
_target_accel_ned_mss.zero();
673
674
// apply jitter-corrected timestamp to this update
675
_last_location_update_ms = _jitter.correct_offboard_timestamp_msec(packet.time_boot_ms, AP_HAL::millis());
676
677
// if sysid not yet set, adopt sender’s sysid and enable automatic sysid tracking
678
if (_sysid == 0) {
679
_sysid.set(msg.sysid);
680
_sysid_used = 0;
681
_estimate_valid = false;
682
_automatic_sysid = true;
683
}
684
685
return true;
686
}
687
688
// Decodes a FOLLOW_TARGET MAVLink message to update the target’s position, velocity, acceleration, and orientation.
689
bool AP_Follow::handle_follow_target_message(const mavlink_message_t &msg)
690
{
691
// decode FOLLOW_TARGET message into packet struct
692
mavlink_follow_target_t packet;
693
mavlink_msg_follow_target_decode(&msg, &packet);
694
695
// ignore message if latitude and longitude are exactly zero (invalid GPS fix)
696
if ((packet.lat == 0 && packet.lon == 0)) {
697
return false;
698
}
699
700
// require that at least position is estimated (bit 0 of est_capabilities)
701
if ((packet.est_capabilities & (1<<0)) == 0) {
702
return false;
703
}
704
705
// build Location object from latitude, longitude, and altitude (alt in meters)
706
const Location target_location {
707
packet.lat,
708
packet.lon,
709
int32_t(packet.alt * 100), // convert meters to centimeters
710
Location::AltFrame::ABSOLUTE
711
};
712
713
// convert global location to local NED frame position
714
Vector3p target_pos_neu_m;
715
if (!target_location.get_vector_from_origin_NEU_m(target_pos_neu_m)) {
716
return false;
717
}
718
719
// adjust Z coordinate to NED frame (NEU altitude -> NED)
720
Location origin;
721
if (!AP::ahrs().get_origin(origin)) {
722
return false;
723
}
724
725
// decode attitude if available (bit 3 of est_capabilities)
726
if (packet.est_capabilities & (1 << 3)) {
727
// reconstruct quaternion from packet
728
Quaternion q{packet.attitude_q[0], packet.attitude_q[1], packet.attitude_q[2], packet.attitude_q[3]};
729
float roll, pitch, yaw;
730
q.to_euler(roll, pitch, yaw);
731
732
// store heading in degrees, wrapped 0–360
733
_target_heading_deg = wrap_360(degrees(yaw));
734
735
// transform body rates (roll, pitch, yaw) to earth frame rates
736
Matrix3f R;
737
q.rotation_matrix(R);
738
Vector3f omega_body = Vector3f{packet.rates[0], packet.rates[1], packet.rates[2]};
739
Vector3f omega_world = R * omega_body; // rotate rates into earth frame
740
741
// store heading rate (yaw rate in world frame) in degrees/sec
742
_target_heading_rate_degs = degrees(omega_world.z);
743
} else if (_offset_type == AP_FOLLOW_OFFSET_TYPE_RELATIVE) {
744
// if attitude unavailable and using relative frame, cannot compute — abort
745
return false;
746
} else {
747
// otherwise, default heading rate to zero
748
_target_heading_rate_degs = 0.0f;
749
}
750
751
_target_pos_ned_m.xy() = target_pos_neu_m.xy();
752
_target_pos_ned_m.z = -packet.alt + origin.alt * 0.01;
753
754
// decode velocity if available (bit 1 of est_capabilities)
755
if (packet.est_capabilities & (1<<1)) {
756
_target_vel_ned_ms.x = packet.vel[0]; // velocity north
757
_target_vel_ned_ms.y = packet.vel[1]; // velocity east
758
_target_vel_ned_ms.z = packet.vel[2]; // velocity down
759
} else {
760
_target_vel_ned_ms.zero();
761
}
762
763
// decode acceleration if available (bit 2 of est_capabilities)
764
if (packet.est_capabilities & (1 << 2)) {
765
_target_accel_ned_mss.x = packet.acc[0]; // acceleration north
766
_target_accel_ned_mss.y = packet.acc[1]; // acceleration east
767
_target_accel_ned_mss.z = packet.acc[2]; // acceleration down
768
} else {
769
_target_accel_ned_mss.zero();
770
}
771
772
// apply jitter-corrected timestamp to this update
773
_last_location_update_ms = _jitter.correct_offboard_timestamp_msec(packet.timestamp, AP_HAL::millis());
774
775
// if sysid not yet assigned, adopt sender's sysid and enable automatic sysid tracking
776
if (_sysid == 0) {
777
_sysid.set(msg.sysid);
778
_automatic_sysid = true;
779
}
780
781
// we are using follow_target: set sysid to sender's sysid
782
_using_follow_target = true;
783
784
return true;
785
}
786
787
788
//==============================================================================
789
// Offset Initialization and Adjustment Functions
790
//==============================================================================
791
792
// Initializes the positional offsets from the target vehicle if not already set.
793
void AP_Follow::init_offsets_if_required()
794
{
795
// return immediately if offsets have already been set
796
if (!_offset_m.get().is_zero()) {
797
return;
798
}
799
_offsets_were_zero = true;
800
801
if (!_estimate_valid) {
802
return;
803
}
804
805
// Check if the target is within the maximum distance
806
Vector3p current_position_ned_m;
807
if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {
808
return;
809
}
810
const Vector3f dist_vec_ned_m = (_target_pos_ned_m - current_position_ned_m).tofloat();
811
812
if ((_offset_type == AP_FOLLOW_OFFSET_TYPE_RELATIVE)) {
813
// rotate offset into vehicle-relative frame based on heading
814
_offset_m.set(rotate_vector(-dist_vec_ned_m, -degrees(_estimate_heading_rad)));
815
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "Relative follow offset loaded");
816
} else {
817
// initialize offset in NED frame (no heading rotation)
818
_offset_m.set(-dist_vec_ned_m);
819
820
// ensure offset type is set to NED frame if initialized this way
821
_offset_type.set(AP_FOLLOW_OFFSET_TYPE_NED);
822
GCS_SEND_TEXT(MAV_SEVERITY_INFO, "N-E-D follow offset loaded");
823
}
824
}
825
826
// Rotates a 3D vector clockwise by the specified angle (degrees).
827
Vector3f AP_Follow::rotate_vector(const Vector3f &vec, float angle_deg) const
828
{
829
// rotate roll, pitch input from north facing to vehicle's perspective
830
Vector3f ret = vec;
831
ret.xy().rotate(radians(angle_deg));
832
833
return ret;
834
}
835
836
// Resets follow mode offsets to zero if they were automatically initialized.
837
void AP_Follow::clear_offsets_if_required()
838
{
839
if (_offsets_were_zero) {
840
_offset_m.set(Vector3f());
841
_offsets_were_zero = false;
842
}
843
}
844
845
846
//==============================================================================
847
// Distance and Bearing Management
848
//==============================================================================
849
850
// Resets the recorded distance and bearing to the target to zero.
851
void AP_Follow::clear_dist_and_bearing_to_target()
852
{
853
_dist_to_target_m = 0.0f;
854
_bearing_to_target_deg = 0.0f;
855
}
856
857
// Updates the recorded distance and bearing to the target to zero.
858
void AP_Follow::update_dist_and_bearing_to_target()
859
{
860
Vector3p current_position_ned_m;
861
if (!AP::ahrs().get_relative_position_NED_origin(current_position_ned_m)) {
862
// if unable to retrieve local position, clear distance/bearing info
863
clear_dist_and_bearing_to_target();
864
} else {
865
// convert vehicle position to NED meters (NEU -> NED and cm -> m)
866
current_position_ned_m.z = -current_position_ned_m.z; // NEU to NED
867
current_position_ned_m *= 0.01; // convert cm to m
868
869
// calculate distance vectors to target, both with and without offsets
870
const Vector3p ofs_dist_vec = _ofs_estimate_pos_ned_m - current_position_ned_m;
871
872
// record distance and bearing to target for reporting/logging
873
if (ofs_dist_vec.xy().is_zero()) {
874
clear_dist_and_bearing_to_target();
875
} else {
876
_dist_to_target_m = ofs_dist_vec.xy().length();
877
_bearing_to_target_deg = degrees(ofs_dist_vec.xy().angle());
878
}
879
}
880
}
881
882
883
//==============================================================================
884
// Logging
885
//==============================================================================
886
887
// Writes a diagnostic onboard log message containing target and vehicle tracking data for Follow mode.
888
#if HAL_LOGGING_ENABLED
889
void AP_Follow::Log_Write_FOLL()
890
{
891
// retrieve latest estimated location and velocity
892
Location loc_estimate{};
893
Vector3f vel_estimate;
894
UNUSED_RESULT(get_target_location_and_velocity(loc_estimate, vel_estimate));
895
896
Location target_location;
897
UNUSED_RESULT(AP::ahrs().get_location_from_origin_offset_NED(target_location, _target_pos_ned_m));
898
899
// log the lead target's reported position and vehicle's estimated position
900
// @LoggerMessage: FOLL
901
// @Description: Follow library diagnostic data
902
// @Field: TimeUS: Time since system startup (microseconds)
903
// @Field: Lat: Target latitude (degrees * 1E7)
904
// @Field: Lon: Target longitude (degrees * 1E7)
905
// @Field: Alt: Target absolute altitude (centimeters)
906
// @Field: VelN: Target velocity, North (m/s)
907
// @Field: VelE: Target velocity, East (m/s)
908
// @Field: VelD: Target velocity, Down (m/s)
909
// @Field: LatE: Vehicle estimated latitude (degrees * 1E7)
910
// @Field: LonE: Vehicle estimated longitude (degrees * 1E7)
911
// @Field: AltE: Vehicle estimated altitude (centimeters)
912
// @Field: FrmE: Vehicle estimated altitude Frame
913
AP::logger().WriteStreaming("FOLL",
914
"TimeUS,Lat,Lon,Alt,VelN,VelE,VelD,LatE,LonE,AltE,FrmE", // labels
915
"sDUmnnnDUm-", // units
916
"F--B000--B-", // mults
917
"QLLifffLLib", // fmt
918
AP_HAL::micros64(),
919
target_location.lat,
920
target_location.lng,
921
target_location.alt,
922
(double)_target_vel_ned_ms.x,
923
(double)_target_vel_ned_ms.y,
924
(double)_target_vel_ned_ms.z,
925
loc_estimate.lat,
926
loc_estimate.lng,
927
loc_estimate.alt,
928
loc_estimate.get_alt_frame()
929
);
930
}
931
#endif // HAL_LOGGING_ENABLED
932
933
934
//==============================================================================
935
// Accessors and Helpers
936
//==============================================================================
937
938
// Returns true if following is enabled and a recent target update has been received.
939
bool AP_Follow::have_target(void) const
940
{
941
if (!_enabled) {
942
return false;
943
}
944
945
// check for timeout
946
if ((_last_location_update_ms == 0) || ((AP_HAL::millis() - _last_location_update_ms) > (uint32_t)(_timeout * 1000.0f))) {
947
return false;
948
}
949
return true;
950
}
951
952
//==============================================================================
953
// AP_Follow Accessor
954
//==============================================================================
955
956
// Accessor for the AP_Follow singleton instance.
957
namespace AP {
958
AP_Follow &follow()
959
{
960
return *AP_Follow::get_singleton();
961
}
962
}
963
964
#endif // AP_FOLLOW_ENABLED
965
966