Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AC_WPNav/AC_WPNav.cpp
9632 views
1
#include <AP_HAL/AP_HAL.h>
2
#include "AC_WPNav.h"
3
4
extern const AP_HAL::HAL& hal;
5
6
// Default waypoint navigation constraints
7
#define WPNAV_WP_SPEED_CMS 1000.0f // default horizontal speed between waypoints (cm/s)
8
#define WPNAV_WP_SPEED_MIN_MS 0.01f // minimum horizontal speed allowed (m/s)
9
#define WPNAV_WP_RADIUS_CM 200.0f // default radius within which a waypoint is considered reached (cm)
10
#define WPNAV_WP_RADIUS_MIN_CM 5.0f // minimum allowable waypoint radius (cm)
11
#define WPNAV_WP_SPEED_UP_CMS 250.0f // default maximum climb speed (cm/s)
12
#define WPNAV_WP_SPEED_DOWN_CMS 150.0f // default maximum descent speed (cm/s)
13
#define WPNAV_WP_ACCEL_Z_DEFAULT_CMSS 100.0f // default vertical acceleration limit (cm/s²)
14
15
const AP_Param::GroupInfo AC_WPNav::var_info[] = {
16
// index 0 was used for the old orientation matrix
17
18
// @Param: SPEED
19
// @DisplayName: Waypoint Horizontal Speed Target
20
// @Description: Defines the speed in cm/s which the aircraft will attempt to maintain horizontally during a WP mission
21
// @Units: cm/s
22
// @Range: 10 2000
23
// @Increment: 50
24
// @User: Standard
25
AP_GROUPINFO("SPEED", 0, AC_WPNav, _wp_speed_cms, WPNAV_WP_SPEED_CMS),
26
27
// @Param: RADIUS
28
// @DisplayName: Waypoint Radius
29
// @Description: Defines the distance from a waypoint, that when crossed indicates the wp has been hit.
30
// @Units: cm
31
// @Range: 5 1000
32
// @Increment: 1
33
// @User: Standard
34
AP_GROUPINFO("RADIUS", 1, AC_WPNav, _wp_radius_cm, WPNAV_WP_RADIUS_CM),
35
36
// @Param: SPEED_UP
37
// @DisplayName: Waypoint Climb Speed Target
38
// @Description: Defines the speed in cm/s which the aircraft will attempt to maintain while climbing during a WP mission
39
// @Units: cm/s
40
// @Range: 10 1000
41
// @Increment: 50
42
// @User: Standard
43
AP_GROUPINFO("SPEED_UP", 2, AC_WPNav, _wp_speed_up_cms, WPNAV_WP_SPEED_UP_CMS),
44
45
// @Param: SPEED_DN
46
// @DisplayName: Waypoint Descent Speed Target
47
// @Description: Defines the speed in cm/s which the aircraft will attempt to maintain while descending during a WP mission
48
// @Units: cm/s
49
// @Range: 10 500
50
// @Increment: 10
51
// @User: Standard
52
AP_GROUPINFO("SPEED_DN", 3, AC_WPNav, _wp_speed_down_cms, WPNAV_WP_SPEED_DOWN_CMS),
53
54
// @Param: ACCEL
55
// @DisplayName: Waypoint Acceleration
56
// @Description: Defines the horizontal acceleration in cm/s/s used during missions
57
// @Units: cm/s/s
58
// @Range: 50 500
59
// @Increment: 10
60
// @User: Standard
61
AP_GROUPINFO("ACCEL", 5, AC_WPNav, _wp_accel_cmss, WPNAV_ACCELERATION_MS * 100.0),
62
63
// @Param: ACCEL_Z
64
// @DisplayName: Waypoint Vertical Acceleration
65
// @Description: Defines the vertical acceleration in cm/s/s used during missions
66
// @Units: cm/s/s
67
// @Range: 50 500
68
// @Increment: 10
69
// @User: Standard
70
AP_GROUPINFO("ACCEL_Z", 6, AC_WPNav, _wp_accel_z_cmss, WPNAV_WP_ACCEL_Z_DEFAULT_CMSS),
71
72
// @Param: RFND_USE
73
// @DisplayName: Waypoint missions use rangefinder for terrain following
74
// @Description: This controls if waypoint missions use rangefinder for terrain following
75
// @Values: 0:Disable,1:Enable
76
// @User: Advanced
77
AP_GROUPINFO("RFND_USE", 10, AC_WPNav, _rangefinder_use, 1),
78
79
// @Param: JERK
80
// @DisplayName: Waypoint Jerk
81
// @Description: Defines the horizontal jerk in m/s/s used during missions
82
// @Units: m/s/s/s
83
// @Range: 1 20
84
// @User: Standard
85
AP_GROUPINFO("JERK", 11, AC_WPNav, _wp_jerk_msss, 1.0f),
86
87
// @Param: TER_MARGIN
88
// @DisplayName: Waypoint Terrain following altitude margin
89
// @Description: Waypoint Terrain following altitude margin. Vehicle will stop if distance from target altitude is larger than this margin (in meters)
90
// @Units: m
91
// @Range: 0.1 100
92
// @User: Advanced
93
AP_GROUPINFO("TER_MARGIN", 12, AC_WPNav, _terrain_margin_m, 10.0),
94
95
// @Param: ACCEL_C
96
// @DisplayName: Waypoint Cornering Acceleration
97
// @Description: Defines the maximum cornering acceleration in cm/s/s used during missions. If zero uses 2x accel value.
98
// @Units: cm/s/s
99
// @Range: 0 500
100
// @Increment: 10
101
// @User: Standard
102
AP_GROUPINFO("ACCEL_C", 13, AC_WPNav, _wp_accel_c_cmss, 0.0),
103
104
AP_GROUPEND
105
};
106
107
// Default constructor.
108
// Note that the Vector/Matrix constructors already implicitly zero their values.
109
AC_WPNav::AC_WPNav(const AP_AHRS_View& ahrs, AC_PosControl& pos_control, const AC_AttitudeControl& attitude_control) :
110
_ahrs(ahrs),
111
_pos_control(pos_control),
112
_attitude_control(attitude_control)
113
{
114
AP_Param::setup_object_defaults(this, var_info);
115
116
// init flags
117
_flags.reached_destination = false;
118
_flags.fast_waypoint = false;
119
120
// initialise old WPNAV_SPEED values
121
_last_wp_speed_cms = _wp_speed_cms;
122
_last_wp_speed_up_cms = _wp_speed_up_cms;
123
_last_wp_speed_down_cms = get_default_speed_down_cms();
124
}
125
126
// Returns the expected source of terrain data when using alt-above-terrain commands.
127
// Used by systems like ModeRTL to determine which terrain provider is active.
128
AC_WPNav::TerrainSource AC_WPNav::get_terrain_source() const
129
{
130
// use range finder if connected
131
// prioritise rangefinder when enabled and available
132
if (_rangefinder_available && _rangefinder_use) {
133
return AC_WPNav::TerrainSource::TERRAIN_FROM_RANGEFINDER;
134
}
135
#if AP_TERRAIN_AVAILABLE
136
const AP_Terrain *terrain = AP::terrain();
137
// use terrain database if enabled
138
if (terrain != nullptr && terrain->enabled()) {
139
return AC_WPNav::TerrainSource::TERRAIN_FROM_TERRAINDATABASE;
140
} else {
141
return AC_WPNav::TerrainSource::TERRAIN_UNAVAILABLE;
142
}
143
#else
144
// fallback if no terrain support is compiled in
145
return AC_WPNav::TerrainSource::TERRAIN_UNAVAILABLE;
146
#endif
147
}
148
149
///
150
/// waypoint navigation
151
///
152
153
// Initializes waypoint and spline navigation using inputs in meters.
154
// Sets speed and acceleration limits, calculates jerk constraints,
155
// and initializes spline or S-curve leg with a defined starting point.
156
void AC_WPNav::wp_and_spline_init_m(float speed_ms, Vector3p stopping_point_ned_m)
157
{
158
// ensure waypoint radius is not below minimum allowed value
159
_wp_radius_cm.set_and_save_ifchanged(MAX(_wp_radius_cm, WPNAV_WP_RADIUS_MIN_CM));
160
161
// ensure waypoint speed is not below minimum allowed value
162
_wp_speed_cms.set_and_save_ifchanged(MAX(_wp_speed_cms, WPNAV_WP_SPEED_MIN_MS * 100.0));
163
164
// initialise position controller
165
_pos_control.D_init_controller_stopping_point();
166
_pos_control.NE_init_controller_stopping_point();
167
168
// determine desired waypoint speed; fallback to default if not provided
169
_check_wp_speed_change = !is_positive(speed_ms);
170
_wp_desired_speed_ne_ms = is_positive(speed_ms) ? speed_ms : get_default_speed_NE_ms();
171
_wp_desired_speed_ne_ms = MAX(_wp_desired_speed_ne_ms, WPNAV_WP_SPEED_MIN_MS);
172
173
// initialise position controller speed and acceleration
174
_pos_control.NE_set_max_speed_accel_m(_wp_desired_speed_ne_ms, get_wp_acceleration_mss());
175
_pos_control.NE_set_correction_speed_accel_m(_wp_desired_speed_ne_ms, get_wp_acceleration_mss());
176
_pos_control.D_set_max_speed_accel_m(get_default_speed_down_ms(), get_default_speed_up_ms(), get_accel_D_mss());
177
_pos_control.D_set_correction_speed_accel_m(get_default_speed_down_ms(), get_default_speed_up_ms(), get_accel_D_mss());
178
179
// calculate jerk limit if not explicitly set by parameter
180
if (!is_positive(_wp_jerk_msss)) {
181
_wp_jerk_msss.set(get_wp_acceleration_mss());
182
}
183
calc_scurve_jerk_and_snap();
184
185
_scurve_prev_leg.init();
186
_scurve_this_leg.init();
187
_scurve_next_leg.init();
188
_track_dt_scalar = 1.0f;
189
190
_flags.reached_destination = true;
191
_flags.fast_waypoint = false;
192
193
// determine initial origin and destination; fallback to current stopping point if not provided
194
if (stopping_point_ned_m.is_zero()) {
195
get_wp_stopping_point_NED_m(stopping_point_ned_m);
196
}
197
_origin_ned_m = _destination_ned_m = stopping_point_ned_m;
198
_is_terrain_alt = false;
199
_this_leg_is_spline = false;
200
201
// initialise velocity profile for terrain margin shaping
202
_offset_vel_ms = _wp_desired_speed_ne_ms;
203
_offset_accel_mss = 0.0;
204
_paused = false;
205
206
// mark as active
207
_wp_last_update_ms = AP_HAL::millis();
208
}
209
210
// Sets the target horizontal speed in cm/s during waypoint navigation.
211
// See set_speed_NE_ms() for full details.
212
void AC_WPNav::set_speed_NE_cms(float speed_cms)
213
{
214
set_speed_NE_ms(speed_cms * 0.01);
215
}
216
217
// Sets the target horizontal speed in m/s during waypoint navigation.
218
// Also updates internal velocity offsets and path shaping limits.
219
void AC_WPNav::set_speed_NE_ms(float speed_ms)
220
{
221
// validate input: speed must be above minimum and current desired speed must be non-zero
222
if (speed_ms >= WPNAV_WP_SPEED_MIN_MS && is_positive(_wp_desired_speed_ne_ms)) {
223
// adjust internal velocity offset to preserve relative motion scaling
224
_offset_vel_ms = speed_ms * _offset_vel_ms / _wp_desired_speed_ne_ms;
225
226
// set new desired horizontal speed for waypoint controller
227
_wp_desired_speed_ne_ms = speed_ms;
228
229
// update horizontal shaping and correction constraints
230
_pos_control.NE_set_max_speed_accel_m(_wp_desired_speed_ne_ms, get_wp_acceleration_mss());
231
_pos_control.NE_set_correction_speed_accel_m(_wp_desired_speed_ne_ms, get_wp_acceleration_mss());
232
233
// update internal trajectory with new limits
234
update_track_with_speed_accel_limits();
235
}
236
}
237
238
// Sets the climb speed for waypoint navigation in m/s.
239
// Updates the vertical controller with the new ascent rate limit.
240
void AC_WPNav::set_speed_up_ms(float speed_up_ms)
241
{
242
// update vertical controller's max speed and accel limits (U axis)
243
_pos_control.D_set_max_speed_accel_m(_pos_control.get_max_speed_down_ms(), speed_up_ms, _pos_control.D_get_max_accel_mss());
244
245
// recompute the trajectory using updated limits
246
update_track_with_speed_accel_limits();
247
}
248
249
// Sets the descent speed for waypoint navigation in m/s.
250
// Updates the vertical controller with the new descent rate limit.
251
void AC_WPNav::set_speed_down_ms(float speed_down_ms)
252
{
253
// update vertical controller descent speed
254
_pos_control.D_set_max_speed_accel_m(speed_down_ms, _pos_control.get_max_speed_up_ms(), _pos_control.D_get_max_accel_mss());
255
256
// recompute the trajectory using updated limits
257
update_track_with_speed_accel_limits();
258
}
259
260
// Sets the current waypoint destination using a Location object.
261
// Converts global coordinates to NED position and sets destination.
262
// arc_rad specifies the signed arc angle in radians for an ARC_WAYPOINT segment (0 for straight path)
263
// Returns false if conversion fails (e.g. missing terrain data).
264
bool AC_WPNav::set_wp_destination_loc(const Location& destination, float arc_rad)
265
{
266
bool is_terrain_alt;
267
Vector3p dest_ned_m;
268
269
// convert Location to NED position vector in meters and determine altitude reference frame
270
if (!get_vector_NED_m(destination, dest_ned_m, is_terrain_alt)) {
271
return false;
272
}
273
274
// apply destination as the active waypoint leg
275
return set_wp_destination_NED_m(dest_ned_m, is_terrain_alt, arc_rad);
276
}
277
278
// Sets the next waypoint destination using a Location object.
279
// Converts global coordinates to NED position and preloads the trajectory.
280
// arc_rad specifies the signed arc angle in radians for an ARC_WAYPOINT segment (0 for straight path)
281
// Returns false if conversion fails or terrain data is unavailable.
282
bool AC_WPNav::set_wp_destination_next_loc(const Location& destination, float arc_rad)
283
{
284
bool is_terrain_alt;
285
Vector3p dest_ned_m;
286
287
// convert Location to NED position vector in meters and determine altitude reference frame
288
if (!get_vector_NED_m(destination, dest_ned_m, is_terrain_alt)) {
289
return false;
290
}
291
292
// apply destination as the next waypoint leg
293
return set_wp_destination_next_NED_m(dest_ned_m, is_terrain_alt, arc_rad);
294
}
295
296
// Gets the current waypoint destination as a Location object.
297
// Altitude frame will be ABOVE_TERRAIN or ABOVE_ORIGIN depending on path configuration.
298
// Returns false if origin is not set or coordinate conversion fails.
299
bool AC_WPNav::get_wp_destination_loc(Location& destination) const
300
{
301
// retrieve global origin for coordinate conversion
302
if (!AP::ahrs().get_origin(destination)) {
303
return false;
304
}
305
306
// convert NED waypoint to global Location format with appropriate altitude frame
307
destination = Location::from_ekf_offset_NED_m(get_wp_destination_NED_m(), _is_terrain_alt ? Location::AltFrame::ABOVE_TERRAIN : Location::AltFrame::ABOVE_ORIGIN);
308
return true;
309
}
310
311
// Sets waypoint destination using NEU position vector in centimeters from EKF origin.
312
// See set_wp_destination_NED_m() for full details.
313
bool AC_WPNav::set_wp_destination_NEU_cm(const Vector3f& destination_neu_cm, bool is_terrain_alt)
314
{
315
Vector3p destination_ned_m = Vector3p(destination_neu_cm.x, destination_neu_cm.y, -destination_neu_cm.z) * 0.01;
316
return set_wp_destination_NED_m(destination_ned_m, is_terrain_alt);
317
}
318
319
// Sets waypoint destination using NED position vector in meters from EKF origin.
320
// If `is_terrain_alt` is true, altitude is interpreted as height above terrain.
321
// Reinitializes the current leg if interrupted, updates origin, and computes trajectory.
322
// arc_rad specifies the signed arc angle in radians for an ARC_WAYPOINT segment (0 for straight path)
323
// Returns false if terrain offset cannot be determined when required.
324
bool AC_WPNav::set_wp_destination_NED_m(const Vector3p& destination_ned_m, bool is_terrain_alt, float arc_rad)
325
{
326
// re-initialise if previous destination has been interrupted
327
if (!is_active() || !_flags.reached_destination) {
328
wp_and_spline_init_m(_wp_desired_speed_ne_ms);
329
}
330
331
_scurve_prev_leg.init();
332
float origin_speed_m = 0.0f;
333
334
// use previous destination as origin
335
_origin_ned_m = _destination_ned_m;
336
337
if (is_terrain_alt == _is_terrain_alt) {
338
if (_this_leg_is_spline) {
339
// Use velocity from end of spline leg to seed new S-curve origin speed
340
Vector3f curr_target_vel_ned_ms = _pos_control.get_vel_desired_NED_ms();
341
curr_target_vel_ned_ms.z -= _pos_control.get_vel_offset_D_ms();
342
origin_speed_m = curr_target_vel_ned_ms.length();
343
} else {
344
// Preserve current leg profile to enable blending with new leg
345
_scurve_prev_leg = _scurve_this_leg;
346
}
347
} else {
348
// Handle transition between terrain-relative and origin-relative altitude frames
349
float terrain_d_m;
350
if (!get_terrain_D_m(terrain_d_m)) {
351
return false;
352
}
353
354
// convert origin to alt-above-terrain if necessary
355
if (is_terrain_alt) {
356
// Convert origin.z to terrain-relative altitude
357
_origin_ned_m.z -= terrain_d_m;
358
_pos_control.init_pos_terrain_D_m(terrain_d_m);
359
} else {
360
// Convert origin.z to origin-relative altitude
361
_origin_ned_m.z += terrain_d_m;
362
_pos_control.init_pos_terrain_D_m(0.0);
363
}
364
}
365
366
// update destination
367
_destination_ned_m = destination_ned_m;
368
_is_terrain_alt = is_terrain_alt;
369
370
if (_flags.fast_waypoint && !_this_leg_is_spline && !_next_leg_is_spline && !_scurve_next_leg.finished()) {
371
// Reuse preloaded next leg if valid to avoid unnecessary recalculation
372
_scurve_this_leg = _scurve_next_leg;
373
} else {
374
// Generate a new S-curve segment to the new destination
375
_scurve_this_leg.calculate_track(_origin_ned_m, _destination_ned_m, arc_rad,
376
_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
377
get_wp_acceleration_mss(), get_accel_D_mss(), get_corner_acceleration_mss(),
378
_scurve_snap_max_mssss, _scurve_jerk_max_msss);
379
if (!is_zero(origin_speed_m)) {
380
// If we have a valid starting speed, seed it into the S-curve
381
_scurve_this_leg.set_origin_speed_max(origin_speed_m);
382
}
383
}
384
385
_this_leg_is_spline = false;
386
_scurve_next_leg.init();
387
_next_destination_ned_m.zero(); // clear next destination_ned_m
388
_flags.fast_waypoint = false; // default waypoint back to slow
389
_flags.reached_destination = false;
390
391
return true;
392
}
393
394
// Sets the next waypoint destination using a NED position vector in meters.
395
// Only updates if terrain frame matches current leg.
396
// Calculates trajectory preview for smoother transition into next segment.
397
// Updates velocity handoff if previous leg is a spline.
398
// arc_rad specifies the signed arc angle in radians for an ARC_WAYPOINT segment (0 for straight path)
399
bool AC_WPNav::set_wp_destination_next_NED_m(const Vector3p& destination_ned_m, bool is_terrain_alt, float arc_rad)
400
{
401
// do not add next point if alt types don't match
402
if (is_terrain_alt != _is_terrain_alt) {
403
return true;
404
}
405
406
// Preload next S-curve leg with current speed and acceleration constraints
407
_scurve_next_leg.calculate_track(_destination_ned_m, destination_ned_m, arc_rad,
408
_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
409
get_wp_acceleration_mss(), get_accel_D_mss(), get_corner_acceleration_mss(),
410
_scurve_snap_max_mssss, _scurve_jerk_max_msss);
411
if (_this_leg_is_spline) {
412
const float this_leg_dest_speed_max_ms = _spline_this_leg.get_destination_speed_max();
413
// Pass velocity forward to allow continuous spline-scurve transition
414
const float next_leg_origin_speed_max_ms = _scurve_next_leg.set_origin_speed_max(this_leg_dest_speed_max_ms);
415
_spline_this_leg.set_destination_speed_max(next_leg_origin_speed_max_ms);
416
}
417
_next_leg_is_spline = false;
418
419
// Enable fast waypoint transition since next leg is known
420
_flags.fast_waypoint = true;
421
422
// Store the upcoming destination for reference
423
_next_destination_ned_m = destination_ned_m;
424
425
return true;
426
}
427
428
// Computes the horizontal stopping point in NE frame, returned in centimeters.
429
// See get_wp_stopping_point_NE_m() for full details.
430
void AC_WPNav::get_wp_stopping_point_NE_cm(Vector2f& stopping_point_ne_cm) const
431
{
432
// convert input/output to meters to match internal representation
433
Vector2p stopping_point_ne_m = stopping_point_ne_cm.topostype() * 0.01;
434
get_wp_stopping_point_NE_m(stopping_point_ne_m);
435
stopping_point_ne_cm = stopping_point_ne_m.tofloat() * 100.0;
436
}
437
438
// Computes the horizontal stopping point in NE frame, in meters, based on current velocity and configured acceleration.
439
// This is the point where the vehicle would come to a stop if decelerated using the configured limits.
440
void AC_WPNav::get_wp_stopping_point_NE_m(Vector2p& stopping_point_ne_m) const
441
{
442
// request stopping point from the position controller
443
_pos_control.get_stopping_point_NE_m(stopping_point_ne_m);
444
}
445
446
// Computes the full 3D NEU stopping point vector in centimeters based on current kinematics.
447
// See get_wp_stopping_point_NED_m() for full details.
448
void AC_WPNav::get_wp_stopping_point_NEU_cm(Vector3f& stopping_point_neu_cm) const
449
{
450
// convert input from cm to m for internal calculation
451
Vector3p stopping_point_ned_m = Vector3p(stopping_point_neu_cm.x, stopping_point_neu_cm.y, -stopping_point_neu_cm.z) * 0.01;
452
// compute stopping point using meters
453
get_wp_stopping_point_NED_m(stopping_point_ned_m);
454
// convert result back to centimeters
455
stopping_point_neu_cm = Vector3f(stopping_point_ned_m.x, stopping_point_ned_m.y, -stopping_point_ned_m.z) * 100.0;
456
}
457
458
// Computes the full 3D NED stopping point in meters based on current velocity and configured acceleration in all axes.
459
// Represents where the vehicle will stop if decelerated from current velocity using configured limits.
460
void AC_WPNav::get_wp_stopping_point_NED_m(Vector3p& stopping_point_ned_m) const
461
{
462
// get horizontal stopping point (North and East)
463
_pos_control.get_stopping_point_NE_m(stopping_point_ned_m.xy());
464
// get vertical stopping point (Down)
465
_pos_control.get_stopping_point_D_m(stopping_point_ned_m.z);
466
}
467
468
// Advances the target location along the current path segment.
469
// Updates target position, velocity, and acceleration based on jerk-limited profile (or spline).
470
// Returns true if the update succeeded (e.g., terrain data was available).
471
bool AC_WPNav::advance_wp_target_along_track(float dt)
472
{
473
// calculate terrain offset if using alt-above-terrain frame
474
float terr_offset_d_m = 0.0f;
475
if (_is_terrain_alt && !get_terrain_D_m(terr_offset_d_m)) {
476
return false;
477
}
478
479
// calculate terrain-based velocity scaling factor
480
const float offset_d_scalar = _pos_control.terrain_scaler_D_m(terr_offset_d_m, get_terrain_margin_m());
481
482
// input shape the terrain offset
483
_pos_control.set_pos_terrain_target_D_m(terr_offset_d_m);
484
485
// get position controller's post-shaped position offset for use in position error computation
486
const Vector3p& psc_pos_offset_ned_m = _pos_control.get_pos_offset_NED_m();
487
488
// compute current position in NED frame, adjusted to destination frame (e.g., terrain-relative if needed)
489
Vector3p curr_pos_ned_m = _pos_control.get_pos_estimate_NED_m() - psc_pos_offset_ned_m;
490
curr_pos_ned_m.z -= terr_offset_d_m;
491
492
// get desired velocity and remove offset
493
Vector3f curr_target_vel_ned_ms = _pos_control.get_vel_desired_NED_ms();
494
curr_target_vel_ned_ms.z -= _pos_control.get_vel_offset_D_ms();
495
496
// scale progression time (track_dt_scalar) based on aircraft’s speed alignment with desired path
497
float track_dt_scalar = 1.0f;
498
if (is_positive(curr_target_vel_ned_ms.length_squared())) {
499
Vector3f track_direction_neu = curr_target_vel_ned_ms.normalized();
500
const float track_error_ned_m = _pos_control.get_pos_error_NED_m().dot(track_direction_neu);
501
const float track_velocity_ned_ms = _pos_control.get_vel_estimate_NED_ms().dot(track_direction_neu);
502
// limit time step scalar to [0,1], with 5% buffer
503
track_dt_scalar = constrain_float(0.05f + (track_velocity_ned_ms - _pos_control.NE_get_pos_p().kP() * track_error_ned_m) / curr_target_vel_ned_ms.length(), 0.0f, 1.0f);
504
}
505
506
// compute velocity scaling (vel_dt_scalar) and apply jerk-limited velocity shaping
507
float vel_dt_scalar = 1.0;
508
if (is_positive(_wp_desired_speed_ne_ms)) {
509
update_vel_accel(_offset_vel_ms, _offset_accel_mss, dt, 0.0, 0.0);
510
const float vel_input_ms = !_paused ? _wp_desired_speed_ne_ms * offset_d_scalar : 0.0;
511
shape_vel_accel(vel_input_ms, 0.0, _offset_vel_ms, _offset_accel_mss, -get_wp_acceleration_mss(), get_wp_acceleration_mss(),
512
_pos_control.get_shaping_jerk_NE_msss(), dt, true);
513
vel_dt_scalar = _offset_vel_ms / _wp_desired_speed_ne_ms;
514
}
515
516
// apply exponential filter to track_dt_scalar using jerk-based time constant
517
float track_dt_scalar_tc = 1.0f;
518
if (!is_zero(_wp_jerk_msss)) {
519
track_dt_scalar_tc = get_wp_acceleration_mss()/_wp_jerk_msss;
520
}
521
_track_dt_scalar += (track_dt_scalar - _track_dt_scalar) * (dt / track_dt_scalar_tc);
522
523
// select path generator (S-curve or spline) and compute target state
524
Vector3p target_pos_ned_m;
525
Vector3f target_vel_ned_ms, target_accel_ned_mss;
526
bool s_finished;
527
if (!_this_leg_is_spline) {
528
// update target position, velocity and acceleration
529
target_pos_ned_m = _origin_ned_m;
530
s_finished = _scurve_this_leg.advance_target_along_track(_scurve_prev_leg, _scurve_next_leg, _wp_radius_cm * 0.01, get_corner_acceleration_mss(), _flags.fast_waypoint, _track_dt_scalar * vel_dt_scalar * dt, target_pos_ned_m, target_vel_ned_ms, target_accel_ned_mss);
531
} else {
532
// splinetarget_vel
533
target_vel_ned_ms = curr_target_vel_ned_ms;
534
_spline_this_leg.advance_target_along_track(_track_dt_scalar * vel_dt_scalar * dt, target_pos_ned_m, target_vel_ned_ms);
535
s_finished = _spline_this_leg.reached_destination();
536
}
537
538
// apply scaled acceleration offset along current direction of travel
539
Vector3f accel_offset_ned_mss;
540
if (is_positive(target_vel_ned_ms.length_squared())) {
541
Vector3f track_direction_neu = target_vel_ned_ms.normalized();
542
accel_offset_ned_mss = track_direction_neu * _offset_accel_mss * target_vel_ned_ms.length() / _wp_desired_speed_ne_ms;
543
}
544
545
target_vel_ned_ms *= vel_dt_scalar;
546
target_accel_ned_mss *= sq(vel_dt_scalar);
547
target_accel_ned_mss += accel_offset_ned_mss;
548
549
// send updated position, velocity, and acceleration targets to position controller
550
_pos_control.set_pos_vel_accel_NED_m(target_pos_ned_m, target_vel_ned_ms, target_accel_ned_mss);
551
552
// check if waypoint has been reached based on mode and radius
553
if (!_flags.reached_destination) {
554
if (s_finished) {
555
// "fast" waypoints are complete once the intermediate point reaches the destination
556
if (_flags.fast_waypoint) {
557
_flags.reached_destination = true;
558
} else {
559
// regular waypoints also require the copter to be within the waypoint radius
560
const Vector3f dist_to_dest_m = (curr_pos_ned_m - _destination_ned_m).tofloat();
561
if (dist_to_dest_m.length_squared() <= sq(_wp_radius_cm * 0.01)) {
562
_flags.reached_destination = true;
563
}
564
}
565
}
566
}
567
568
// successfully advanced along track
569
return true;
570
}
571
572
// Updates the current and next path segment to reflect new speed and acceleration limits.
573
// Should be called after modifying NE/U controller limits or vehicle configuration.
574
void AC_WPNav::update_track_with_speed_accel_limits()
575
{
576
// update speed and acceleration limits for the current segment
577
if (_this_leg_is_spline) {
578
_spline_this_leg.set_speed_accel(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
579
get_wp_acceleration_mss(), get_accel_D_mss());
580
} else {
581
_scurve_this_leg.set_speed_max(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms());
582
}
583
584
// update speed and acceleration limits for the next segment
585
if (_next_leg_is_spline) {
586
_spline_next_leg.set_speed_accel(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
587
get_wp_acceleration_mss(), get_accel_D_mss());
588
} else {
589
_scurve_next_leg.set_speed_max(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms());
590
}
591
}
592
593
// Returns the horizontal distance to the destination waypoint in centimeters.
594
// See get_wp_distance_to_destination_m() for full details.
595
float AC_WPNav::get_wp_distance_to_destination_cm() const
596
{
597
// convert distance from meters to centimeters
598
return get_wp_distance_to_destination_m() * 100.0;
599
}
600
601
// Returns the horizontal distance in meters between the current position and the destination waypoint.
602
float AC_WPNav::get_wp_distance_to_destination_m() const
603
{
604
// calculate 2D ground distance between current position and destination in NE frame
605
return get_horizontal_distance(_pos_control.get_pos_estimate_NED_m().xy().tofloat(), _destination_ned_m.xy().tofloat());
606
}
607
608
// Returns the bearing to the current waypoint destination in centidegrees.
609
// See get_wp_bearing_to_destination_rad() for full details.
610
int32_t AC_WPNav::get_wp_bearing_to_destination_cd() const
611
{
612
// compute heading from current position to destination in centidegrees
613
return get_bearing_cd(_pos_control.get_pos_estimate_NED_m().xy().tofloat(), _destination_ned_m.xy().tofloat());
614
}
615
616
// Returns the bearing to the current waypoint destination in radians.
617
// The bearing is measured clockwise from North, with 0 = North.
618
float AC_WPNav::get_wp_bearing_to_destination_rad() const
619
{
620
// compute heading from current position to destination in radians
621
return get_bearing_rad(_pos_control.get_pos_estimate_NED_m().xy().tofloat(), _destination_ned_m.xy().tofloat());
622
}
623
624
// Runs the waypoint navigation controller.
625
// Advances the target position and updates the position controller.
626
// Should be called at 100 Hz or higher for accurate tracking.
627
bool AC_WPNav::update_wpnav()
628
{
629
// check for changes in WPNAV_SPEED parameter (horizontal speed target)
630
if (_check_wp_speed_change) {
631
if (!is_equal(_wp_speed_cms.get(), _last_wp_speed_cms)) {
632
// apply new WPNAV_SPEED value
633
set_speed_NE_ms(get_default_speed_NE_ms());
634
_last_wp_speed_cms = _wp_speed_cms;
635
}
636
}
637
// check for climb and descent speed updates
638
if (!is_equal(_wp_speed_up_cms.get(), _last_wp_speed_up_cms)) {
639
set_speed_up_ms(get_default_speed_up_ms());
640
_last_wp_speed_up_cms = _wp_speed_up_cms;
641
}
642
if (!is_equal(_wp_speed_down_cms.get(), _last_wp_speed_down_cms)) {
643
set_speed_down_ms(get_default_speed_down_ms());
644
_last_wp_speed_down_cms = _wp_speed_down_cms;
645
}
646
647
// advance the waypoint target based on current position and timing
648
bool ret = true;
649
if (!advance_wp_target_along_track(_pos_control.get_dt_s())) {
650
// To-Do: handle inability to advance along track (probably because of missing terrain data)
651
ret = false;
652
}
653
654
// run the horizontal position controller
655
_pos_control.NE_update_controller();
656
657
// record update time for is_active()
658
_wp_last_update_ms = AP_HAL::millis();
659
660
return ret;
661
}
662
663
// Returns true if update_wpnav() has been called within the last 200 ms.
664
// Used to check if waypoint navigation is currently active.
665
bool AC_WPNav::is_active() const
666
{
667
// consider WPNAV active if last update was within 200 milliseconds
668
return (AP_HAL::millis() - _wp_last_update_ms) < 200;
669
}
670
671
// Forces a stop at the next waypoint instead of continuing to the subsequent one.
672
// Used by Dijkstra’s object avoidance when the future path is obstructed.
673
// Only affects regular (non-spline) waypoints.
674
// Returns true if the stop behavior was newly enforced.
675
bool AC_WPNav::force_stop_at_next_wp()
676
{
677
// skip if vehicle was already going to stop at the next waypoint
678
if (!_flags.fast_waypoint) {
679
return false;
680
}
681
682
_flags.fast_waypoint = false;
683
684
// override final velocity for current leg and reset preview for next leg
685
if (!_this_leg_is_spline) {
686
_scurve_this_leg.set_destination_speed_max(0);
687
}
688
if (!_next_leg_is_spline) {
689
_scurve_next_leg.init();
690
}
691
692
return true;
693
}
694
695
// Returns terrain offset in meters above the EKF origin at the current position.
696
// Positive values mean terrain lies above the EKF origin altitude.
697
// Source may be rangefinder or terrain database depending on availability.
698
bool AC_WPNav::get_terrain_U_m(float& terrain_u_m)
699
{
700
// determine terrain data source and compute offset accordingly
701
switch (get_terrain_source()) {
702
case AC_WPNav::TerrainSource::TERRAIN_UNAVAILABLE:
703
return false;
704
705
case AC_WPNav::TerrainSource::TERRAIN_FROM_RANGEFINDER:
706
if (_rangefinder_healthy) {
707
// return previously saved offset based on rangefinder
708
terrain_u_m = _rangefinder_terrain_u_m;
709
return true;
710
}
711
return false;
712
713
case AC_WPNav::TerrainSource::TERRAIN_FROM_TERRAINDATABASE:
714
#if AP_TERRAIN_AVAILABLE
715
float height_above_terrain_m = 0.0f;
716
AP_Terrain *terrain = AP::terrain();
717
if (terrain != nullptr &&
718
terrain->height_above_terrain(height_above_terrain_m, true)) {
719
// compute offset as difference between current altitude and terrain height
720
terrain_u_m = _pos_control.get_pos_estimate_U_m() - height_above_terrain_m;
721
return true;
722
}
723
#endif
724
return false;
725
}
726
727
// unreachable fallback path
728
return false;
729
}
730
bool AC_WPNav::get_terrain_D_m(float& terrain_d_m)
731
{
732
if (!get_terrain_U_m(terrain_d_m)) {
733
return false;
734
}
735
terrain_d_m *= -1.0;
736
return true;
737
}
738
739
///
740
/// spline methods
741
///
742
743
// Sets the current spline waypoint using global coordinates.
744
// Converts `destination` and `next_destination` to NED position vectors and sets up a spline between them.
745
// Returns false if conversion from location to vector fails.
746
bool AC_WPNav::set_spline_destination_loc(const Location& destination, const Location& next_destination, bool next_is_spline)
747
{
748
// convert destination location to NED vector and altitude frame
749
Vector3p dest_ned_m;
750
bool dest_is_terrain_alt;
751
if (!get_vector_NED_m(destination, dest_ned_m, dest_is_terrain_alt)) {
752
return false;
753
}
754
755
// convert next destination location to NED vector and altitude frame
756
Vector3p next_dest_ned_m;
757
bool next_dest_is_terrain_alt;
758
if (!get_vector_NED_m(next_destination, next_dest_ned_m, next_dest_is_terrain_alt)) {
759
return false;
760
}
761
762
// initialize spline path between converted destination and next_destination
763
return set_spline_destination_NED_m(dest_ned_m, dest_is_terrain_alt, next_dest_ned_m, next_dest_is_terrain_alt, next_is_spline);
764
}
765
766
// Sets the next spline segment using global coordinates.
767
// Converts the next and next-next destinations to NED position vectors and initializes the spline transition.
768
// Returns false if any conversion from location to vector fails.
769
bool AC_WPNav::set_spline_destination_next_loc(const Location& next_destination, const Location& next_next_destination, bool next_next_is_spline)
770
{
771
// convert next_destination location to NED vector and determine if it uses terrain-relative altitude
772
Vector3p next_dest_ned_m;
773
bool next_dest_is_terrain_alt;
774
if (!get_vector_NED_m(next_destination, next_dest_ned_m, next_dest_is_terrain_alt)) {
775
return false;
776
}
777
778
// convert next_next_destination location to NED vector and determine if it uses terrain-relative altitude
779
Vector3p next_next_dest_ned_m;
780
bool next_next_dest_is_terr_alt;
781
if (!get_vector_NED_m(next_next_destination, next_next_dest_ned_m, next_next_dest_is_terr_alt)) {
782
return false;
783
}
784
785
// initialize next spline segment using converted waypoints
786
return set_spline_destination_next_NED_m(next_dest_ned_m, next_dest_is_terrain_alt, next_next_dest_ned_m, next_next_dest_is_terr_alt, next_next_is_spline);
787
}
788
789
// Sets the current spline waypoint using NED position vectors in meters.
790
// Initializes a spline path from `destination_ned_m` to `next_destination_ned_m`, respecting terrain altitude framing.
791
// Both waypoints must use the same altitude frame (either above terrain or above origin).
792
// Returns false if terrain altitude cannot be determined when required.
793
bool AC_WPNav::set_spline_destination_NED_m(const Vector3p& destination_ned_m, bool is_terrain_alt, const Vector3p& next_destination_ned_m, bool next_is_terrain_alt, bool next_is_spline)
794
{
795
// re-initialise path state if previous destination was not completed or controller inactive
796
if (!is_active() || !_flags.reached_destination) {
797
wp_and_spline_init_m(_wp_desired_speed_ne_ms);
798
}
799
800
// update spline calculators speeds and accelerations
801
_spline_this_leg.set_speed_accel(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
802
_pos_control.NE_get_max_accel_mss(), _pos_control.D_get_max_accel_mss());
803
804
// calculate origin and origin velocity vector
805
Vector3f origin_vector_ned_m;
806
if (is_terrain_alt == _is_terrain_alt) {
807
if (_flags.fast_waypoint) {
808
// calculate origin vector
809
if (_this_leg_is_spline) {
810
// use velocity vector from end of previous spline segment
811
origin_vector_ned_m = _spline_this_leg.get_destination_vel();
812
} else {
813
// use direction vector from previous straight-line segment
814
origin_vector_ned_m = (_destination_ned_m - _origin_ned_m).tofloat();
815
}
816
}
817
818
// use previous destination as origin
819
_origin_ned_m = _destination_ned_m;
820
} else {
821
822
// use previous destination as origin
823
_origin_ned_m = _destination_ned_m;
824
825
// get current alt above terrain
826
float terrain_d_m;
827
if (!get_terrain_D_m(terrain_d_m)) {
828
return false;
829
}
830
831
// convert origin to alt-above-terrain if necessary
832
if (is_terrain_alt) {
833
// new destination is alt-above-terrain, previous destination was alt-above-ekf-origin
834
_origin_ned_m.z -= terrain_d_m;
835
_pos_control.init_pos_terrain_D_m(terrain_d_m);
836
} else {
837
// new destination is alt-above-ekf-origin, previous destination was alt-above-terrain
838
_origin_ned_m.z += terrain_d_m;
839
_pos_control.init_pos_terrain_D_m(0.0);
840
}
841
}
842
843
// store destination location
844
_destination_ned_m = destination_ned_m;
845
_is_terrain_alt = is_terrain_alt;
846
847
// calculate destination velocity vector
848
Vector3f destination_vector_ned_m;
849
if (is_terrain_alt == next_is_terrain_alt) {
850
if (next_is_spline) {
851
// aim to leave segment in direction of full arc (origin to next)
852
destination_vector_ned_m = (next_destination_ned_m - _origin_ned_m).tofloat();
853
} else {
854
// aim to leave segment in direction of following leg
855
destination_vector_ned_m = (next_destination_ned_m - _destination_ned_m).tofloat();
856
}
857
}
858
_flags.fast_waypoint = !destination_vector_ned_m.is_zero();
859
860
// setup spline leg using origin and destination vectors
861
_spline_this_leg.set_origin_and_destination(_origin_ned_m, _destination_ned_m, origin_vector_ned_m, destination_vector_ned_m);
862
_this_leg_is_spline = true;
863
_flags.reached_destination = false;
864
865
return true;
866
}
867
868
// Sets the next spline segment using NED position vectors in meters.
869
// Creates a spline path from the current destination to `next_destination_ned_m`, and prepares transition toward `next_next_destination_ned_m`.
870
// All waypoints must use the same altitude frame (above terrain or origin).
871
// Returns false if terrain data is missing and required.
872
bool AC_WPNav::set_spline_destination_next_NED_m(const Vector3p& next_destination_ned_m, bool next_is_terrain_alt, const Vector3p& next_next_destination_ned_m, bool next_next_is_terrain_alt, bool next_next_is_spline)
873
{
874
// do not add next point if alt types don't match
875
if (next_is_terrain_alt != _is_terrain_alt) {
876
return true;
877
}
878
879
// calculate origin and origin velocity vector
880
Vector3f origin_vector_ned_m;
881
if (_this_leg_is_spline) {
882
// use final velocity vector from current spline segment
883
origin_vector_ned_m = _spline_this_leg.get_destination_vel();
884
} else {
885
// use vector from previous origin to current destination
886
origin_vector_ned_m = (_destination_ned_m - _origin_ned_m).tofloat();
887
}
888
889
// calculate destination velocity vector
890
Vector3f destination_vector_ned_m;
891
if (next_is_terrain_alt == next_next_is_terrain_alt) {
892
if (next_next_is_spline) {
893
// aim to leave segment in direction of the arc from current to next-next
894
destination_vector_ned_m = (next_next_destination_ned_m - _destination_ned_m).tofloat();
895
} else {
896
// aim to leave segment in direction of the upcoming straight leg
897
destination_vector_ned_m = (next_next_destination_ned_m - next_destination_ned_m).tofloat();
898
}
899
}
900
901
// update spline calculators speeds and accelerations
902
_spline_next_leg.set_speed_accel(_pos_control.NE_get_max_speed_ms(), _pos_control.get_max_speed_up_ms(), _pos_control.get_max_speed_down_ms(),
903
_pos_control.NE_get_max_accel_mss(), _pos_control.D_get_max_accel_mss());
904
905
// setup next spline leg. Note this could be made local
906
_spline_next_leg.set_origin_and_destination(_destination_ned_m, next_destination_ned_m, origin_vector_ned_m, destination_vector_ned_m);
907
_next_leg_is_spline = true;
908
909
// next destination provided so fast waypoint
910
_flags.fast_waypoint = true;
911
912
// update this_leg's final velocity to match next spline leg
913
if (!_this_leg_is_spline) {
914
_scurve_this_leg.set_destination_speed_max(_spline_next_leg.get_origin_speed_max());
915
} else {
916
_spline_this_leg.set_destination_speed_max(_spline_next_leg.get_origin_speed_max());
917
}
918
919
return true;
920
}
921
922
// Converts a Location to a NED position vector in meters from the EKF origin.
923
// Sets `is_terrain_alt` to true if the resulting Z position is relative to terrain.
924
// Returns false if terrain data is unavailable or conversion fails.
925
bool AC_WPNav::get_vector_NED_m(const Location &loc, Vector3p &pos_from_origin_ned_m, bool &is_terrain_alt)
926
{
927
// convert horizontal position (latitude/longitude) to NE vector from EKF origin
928
Vector2p loc_pos_from_origin_ned_m;
929
if (!loc.get_vector_xy_from_origin_NE_m(loc_pos_from_origin_ned_m)) {
930
return false;
931
}
932
933
// convert altitude
934
if (loc.get_alt_frame() == Location::AltFrame::ABOVE_TERRAIN) {
935
float terrain_u_m;
936
if (!loc.get_alt_m(Location::AltFrame::ABOVE_TERRAIN, terrain_u_m)) {
937
return false;
938
}
939
pos_from_origin_ned_m.z = -terrain_u_m;
940
is_terrain_alt = true;
941
} else {
942
is_terrain_alt = false;
943
float origin_alt_m;
944
if (!loc.get_alt_m(Location::AltFrame::ABOVE_ORIGIN, origin_alt_m)) {
945
return false;
946
}
947
pos_from_origin_ned_m.z = -origin_alt_m;
948
is_terrain_alt = false;
949
}
950
951
// set horizontal components (x/y) of NED vector after successful conversion
952
pos_from_origin_ned_m.xy() = loc_pos_from_origin_ned_m;
953
954
return true;
955
}
956
957
// Calculates s-curve jerk and snap limits based on attitude controller capabilities.
958
// Updates _scurve_jerk_max_msss and _scurve_snap_max_mssss with constrained values.
959
void AC_WPNav::calc_scurve_jerk_and_snap()
960
{
961
// calculate max horizontal jerk based on roll/pitch angular velocity limits
962
_scurve_jerk_max_msss = MIN(_attitude_control.get_ang_vel_roll_max_rads() * GRAVITY_MSS, _attitude_control.get_ang_vel_pitch_max_rads() * GRAVITY_MSS);
963
if (is_zero(_scurve_jerk_max_msss)) {
964
_scurve_jerk_max_msss = _wp_jerk_msss;
965
} else {
966
_scurve_jerk_max_msss = MIN(_scurve_jerk_max_msss, _wp_jerk_msss);
967
}
968
969
// calculate maximum snap (rate of change of jerk)
970
// uses input time constant as proxy for lean angle actuation latency
971
_scurve_snap_max_mssss = (_scurve_jerk_max_msss * M_PI) / (2.0 * MAX(_attitude_control.get_input_tc(), 0.1f));
972
973
// constrain snap based on roll/pitch angular acceleration capability
974
const float snap = MIN(_attitude_control.get_accel_roll_max_radss(), _attitude_control.get_accel_pitch_max_radss()) * GRAVITY_MSS;
975
if (is_positive(snap)) {
976
_scurve_snap_max_mssss = MIN(_scurve_snap_max_mssss, snap);
977
}
978
979
// apply safety margin: reduce snap to 50% of calculated maximum
980
_scurve_snap_max_mssss *= 0.5;
981
}
982
983