Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AC_WPNav/AC_WPNav_OA.cpp
9660 views
1
#include "AC_WPNav_config.h"
2
3
#if AC_WPNAV_OA_ENABLED
4
5
#include <AP_Math/control.h>
6
#include <AP_InternalError/AP_InternalError.h>
7
#include "AC_WPNav_OA.h"
8
9
AC_WPNav_OA::AC_WPNav_OA(const AP_AHRS_View& ahrs, AC_PosControl& pos_control, const AC_AttitudeControl& attitude_control) :
10
AC_WPNav(ahrs, pos_control, attitude_control)
11
{
12
}
13
14
// Returns the object-avoidance-adjusted waypoint location (in global coordinates).
15
// Falls back to original destination if OA is not active.
16
bool AC_WPNav_OA::get_oa_wp_destination(Location& destination) const
17
{
18
// Return unmodified global destination if OA is not active
19
if (_oa_state == AP_OAPathPlanner::OA_NOT_REQUIRED) {
20
return get_wp_destination_loc(destination);
21
}
22
23
// OA is active — return path-planner-adjusted intermediate destination
24
destination = _oa_destination;
25
return true;
26
}
27
28
// Sets the waypoint destination using NEU coordinates in centimeters.
29
// See set_wp_destination_NED_m() for full details.
30
bool AC_WPNav_OA::set_wp_destination_NEU_cm(const Vector3f& destination_neu_cm, bool is_terrain_alt)
31
{
32
// Convert input from NEU centimeters to NED meters and delegate to meter version
33
Vector3p destination_ned_m = Vector3p(destination_neu_cm.x, destination_neu_cm.y, -destination_neu_cm.z) * 0.01;
34
return set_wp_destination_NED_m(destination_ned_m, is_terrain_alt);
35
}
36
37
// Sets the waypoint destination using NED coordinates in meters.
38
// - destination_ned_m: NED offset from EKF origin in meters.
39
// - is_terrain_alt: true if the destination_ned_m is relative to the terrain surface.
40
// - Resets OA state on success.
41
bool AC_WPNav_OA::set_wp_destination_NED_m(const Vector3p& destination_ned_m, bool is_terrain_alt, float arc_ang_rad)
42
{
43
// Call base implementation to set destination and terrain-altitude flag
44
const bool ret = AC_WPNav::set_wp_destination_NED_m(destination_ned_m, is_terrain_alt, arc_ang_rad);
45
46
// If destination set successfully, reset OA state to inactive
47
if (ret) {
48
// reset object avoidance state
49
_oa_state = AP_OAPathPlanner::OA_NOT_REQUIRED;
50
}
51
52
return ret;
53
}
54
55
// Returns the horizontal distance to the final destination in centimeters.
56
// See get_wp_distance_to_destination_m() for full details.
57
float AC_WPNav_OA::get_wp_distance_to_destination_cm() const
58
{
59
// Convert horizontal distance from meters to centimeters
60
return get_wp_distance_to_destination_m() * 100.0;
61
}
62
63
// Returns the horizontal distance to the final destination in meters.
64
// Ignores OA-adjusted targets and always measures to the original final destination.
65
float AC_WPNav_OA::get_wp_distance_to_destination_m() const
66
{
67
// Return horizontal distance to final destination (ignoring OA intermediate goals)
68
if (_oa_state == AP_OAPathPlanner::OA_NOT_REQUIRED) {
69
return AC_WPNav::get_wp_distance_to_destination_m();
70
}
71
72
// Compute distance to original destination using backed-up NEU position
73
return get_horizontal_distance(_pos_control.get_pos_estimate_NED_m().xy(), _destination_oabak_ned_m.xy());
74
}
75
76
// Returns the bearing to the final destination in centidegrees.
77
// See get_wp_bearing_to_destination_rad() for full details.
78
int32_t AC_WPNav_OA::get_wp_bearing_to_destination_cd() const
79
{
80
// Convert bearing to destination (in radians) to centidegrees
81
return rad_to_cd(get_wp_bearing_to_destination_rad());
82
}
83
84
// Returns the bearing to the final destination in radians.
85
// Ignores OA-adjusted targets and always calculates from original final destination.
86
float AC_WPNav_OA::get_wp_bearing_to_destination_rad() const
87
{
88
// Use base class method if object avoidance is inactive
89
if (_oa_state == AP_OAPathPlanner::OA_NOT_REQUIRED) {
90
return AC_WPNav::get_wp_bearing_to_destination_rad();
91
}
92
93
// Return bearing to the original destination, not the OA-adjusted one
94
return get_bearing_rad(_pos_control.get_pos_estimate_NED_m().xy().tofloat(), _destination_oabak_ned_m.xy().tofloat());
95
}
96
97
// Returns true if the vehicle has reached the final destination within radius threshold.
98
// Ignores OA-adjusted intermediate destinations.
99
bool AC_WPNav_OA::reached_wp_destination() const
100
{
101
// Only consider the waypoint reached if OA is inactive and base class condition is met
102
return (_oa_state == AP_OAPathPlanner::OA_NOT_REQUIRED) && AC_WPNav::reached_wp_destination();
103
}
104
105
// Runs the waypoint navigation update loop, including OA path planning logic.
106
// Delegates to parent class if OA is not active or not required.
107
bool AC_WPNav_OA::update_wpnav()
108
{
109
// Run path planning logic using the active OA planner
110
AP_OAPathPlanner *oa_ptr = AP_OAPathPlanner::get_singleton();
111
Location current_loc;
112
if ((oa_ptr != nullptr) && AP::ahrs().get_location(current_loc)) {
113
114
// Backup current path state before OA modifies it
115
if (_oa_state == AP_OAPathPlanner::OA_NOT_REQUIRED) {
116
_origin_oabak_ned_m = _origin_ned_m;
117
_destination_oabak_ned_m = _destination_ned_m;
118
_is_terrain_alt_oabak = _is_terrain_alt;
119
_next_destination_oabak_ned_m = _next_destination_ned_m;
120
}
121
122
// Convert backup path state to global Location objects for planner input
123
const Location origin_loc = Location::from_ekf_offset_NED_m(_origin_oabak_ned_m, _is_terrain_alt_oabak ? Location::AltFrame::ABOVE_TERRAIN : Location::AltFrame::ABOVE_ORIGIN);
124
const Location destination_loc = Location::from_ekf_offset_NED_m(_destination_oabak_ned_m, _is_terrain_alt_oabak ? Location::AltFrame::ABOVE_TERRAIN : Location::AltFrame::ABOVE_ORIGIN);
125
const Location next_destination_loc = Location::from_ekf_offset_NED_m(_next_destination_oabak_ned_m, _is_terrain_alt_oabak ? Location::AltFrame::ABOVE_TERRAIN : Location::AltFrame::ABOVE_ORIGIN);
126
Location oa_origin_new, oa_destination_new, oa_next_destination_new;
127
bool dest_to_next_dest_clear = true;
128
AP_OAPathPlanner::OAPathPlannerUsed path_planner_used = AP_OAPathPlanner::OAPathPlannerUsed::None;
129
130
// Request obstacle-avoidance-adjusted path from planner
131
const AP_OAPathPlanner::OA_RetState oa_retstate = oa_ptr->mission_avoidance(current_loc,
132
origin_loc,
133
destination_loc,
134
next_destination_loc,
135
oa_origin_new,
136
oa_destination_new,
137
oa_next_destination_new,
138
dest_to_next_dest_clear,
139
path_planner_used);
140
141
switch (oa_retstate) {
142
143
case AP_OAPathPlanner::OA_NOT_REQUIRED:
144
// OA is no longer needed — restore original destination and optionally set next
145
if (_oa_state != oa_retstate) {
146
// object avoidance has become inactive so reset target to original destination
147
if (!set_wp_destination_NED_m(_destination_oabak_ned_m, _is_terrain_alt_oabak)) {
148
// trigger terrain failsafe
149
return false;
150
}
151
152
// if path from destination to next_destination is clear
153
if (dest_to_next_dest_clear && (oa_ptr->get_options() & AP_OAPathPlanner::OA_OPTION_FAST_WAYPOINTS)) {
154
// set next destination if non-zero
155
if (!_next_destination_oabak_ned_m.is_zero()) {
156
set_wp_destination_next_NED_m(_next_destination_oabak_ned_m);
157
}
158
}
159
_oa_state = oa_retstate;
160
}
161
162
// Prevent transitioning past this waypoint if path to next is unclear
163
// Note that this check is run on every iteration even if the path planner is not active
164
if (!dest_to_next_dest_clear) {
165
force_stop_at_next_wp();
166
}
167
break;
168
169
case AP_OAPathPlanner::OA_PROCESSING:
170
// Allow continued movement while OA path is processing if fast-waypointing is enabled
171
if (oa_ptr->get_options() & AP_OAPathPlanner::OA_OPTION_FAST_WAYPOINTS) {
172
// if fast waypoint option is set, proceed during processing
173
break;
174
}
175
FALLTHROUGH;
176
177
case AP_OAPathPlanner::OA_ERROR:
178
// OA temporarily failing — stop vehicle at current position
179
if ((_oa_state != AP_OAPathPlanner::OA_PROCESSING) && (_oa_state != AP_OAPathPlanner::OA_ERROR)) {
180
// calculate stopping point
181
Vector3p stopping_point_ned_m;
182
get_wp_stopping_point_NED_m(stopping_point_ned_m);
183
_oa_destination = Location::from_ekf_offset_NED_m(stopping_point_ned_m, Location::AltFrame::ABOVE_ORIGIN);
184
_oa_next_destination.zero();
185
if (set_wp_destination_NED_m(stopping_point_ned_m, false)) {
186
_oa_state = oa_retstate;
187
}
188
}
189
break;
190
191
case AP_OAPathPlanner::OA_SUCCESS:
192
193
// Handle result differently depending on which OA planner was used
194
switch (path_planner_used) {
195
196
case AP_OAPathPlanner::OAPathPlannerUsed::None:
197
// this should never happen. this means the path planner has returned success but has failed to set the path planner used
198
INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
199
return false;
200
201
case AP_OAPathPlanner::OAPathPlannerUsed::Dijkstras:
202
// Dijkstra's. Action is only needed if path planner has just became active or the target destination's lat or lon has changed
203
// Interpolate altitude and set new target if different or first OA success
204
if ((_oa_state != AP_OAPathPlanner::OA_SUCCESS) || !oa_destination_new.same_latlon_as(_oa_destination)) {
205
Location origin_oabak_loc = Location::from_ekf_offset_NED_m(_origin_oabak_ned_m, _is_terrain_alt_oabak ? Location::AltFrame::ABOVE_TERRAIN : Location::AltFrame::ABOVE_ORIGIN);
206
Location destination_oabak_loc = Location::from_ekf_offset_NED_m(_destination_oabak_ned_m, _is_terrain_alt_oabak ? Location::AltFrame::ABOVE_TERRAIN : Location::AltFrame::ABOVE_ORIGIN);
207
oa_destination_new.linearly_interpolate_alt(origin_oabak_loc, destination_oabak_loc);
208
209
// set new OA adjusted destination
210
if (!set_wp_destination_loc(oa_destination_new)) {
211
// trigger terrain failsafe
212
return false;
213
}
214
// if new target set successfully, update oa state and destination
215
_oa_state = oa_retstate;
216
_oa_destination = oa_destination_new;
217
218
// Set next destination if provided
219
if ((oa_ptr->get_options() & AP_OAPathPlanner::OA_OPTION_FAST_WAYPOINTS) && !oa_next_destination_new.is_zero()) {
220
// calculate oa_next_destination_new's altitude using linear interpolation between original origin and destination
221
// this "next destination" is still an intermediate point between the origin and destination
222
oa_next_destination_new.linearly_interpolate_alt(origin_oabak_loc, destination_oabak_loc);
223
if (set_wp_destination_next_loc(oa_next_destination_new)) {
224
_oa_next_destination = oa_next_destination_new;
225
}
226
}
227
}
228
break;
229
230
case AP_OAPathPlanner::OAPathPlannerUsed::BendyRulerHorizontal: {
231
_oa_state = oa_retstate;
232
_oa_destination = oa_destination_new;
233
234
// Adjust altitude based on current progress along the path
235
Location target_alt_loc = current_loc;
236
target_alt_loc.linearly_interpolate_alt(origin_loc, destination_loc);
237
238
// Get terrain offset if needed
239
float terrain_d_m = 0;
240
if (_is_terrain_alt_oabak && !get_terrain_D_m(terrain_d_m)) {
241
// trigger terrain failsafe
242
return false;
243
}
244
245
// Convert global destination to NEU vector and pass directly to position controller
246
Vector2f destination_ne_m;
247
if (!_oa_destination.get_vector_xy_from_origin_NE_m(destination_ne_m)) {
248
// this should never happen because we can only get here if we have an EKF origin
249
INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
250
return false;
251
}
252
float target_alt_loc_alt_m = 0;
253
UNUSED_RESULT(target_alt_loc.get_alt_m(target_alt_loc.get_alt_frame(), target_alt_loc_alt_m));
254
Vector3p destination_ned_m{destination_ne_m.x, destination_ne_m.y, target_alt_loc_alt_m};
255
256
// pass the desired position directly to the position controller
257
_pos_control.input_pos_NED_m(destination_ned_m, terrain_d_m, 10.0);
258
259
// update horizontal position controller (vertical is updated in vehicle code)
260
_pos_control.NE_update_controller();
261
262
// return success without calling parent AC_WPNav
263
return true;
264
}
265
266
case AP_OAPathPlanner::OAPathPlannerUsed::BendyRulerVertical: {
267
_oa_state = oa_retstate;
268
_oa_destination = oa_destination_new;
269
270
// Convert final destination to NEU offset and push to position controller
271
Vector3p destination_ned_m;
272
if (!_oa_destination.get_vector_from_origin_NED_m(destination_ned_m)) {
273
// this should never happen because we can only get here if we have an EKF origin
274
INTERNAL_ERROR(AP_InternalError::error_t::flow_of_control);
275
return false;
276
}
277
278
// pass the desired position directly to the position controller as an offset from EKF origin in NEU
279
_pos_control.input_pos_NED_m(destination_ned_m, 0, 10.0);
280
281
// update horizontal position controller (vertical is updated in vehicle code)
282
_pos_control.NE_update_controller();
283
284
// return success without calling parent AC_WPNav
285
return true;
286
}
287
288
}
289
}
290
}
291
292
// Run standard waypoint update if OA was not active or handled above
293
return AC_WPNav::update_wpnav();
294
}
295
296
#endif // Ac_WPNAV_OA_ENABLED
297
298