Line data Source code
1 : #include "Plane.h"
2 :
3 : /********************************************************************************/
4 : // Command Event Handlers
5 : /********************************************************************************/
6 1337 : bool Plane::start_command(const AP_Mission::Mission_Command& cmd)
7 : {
8 1329 : if (control_mode == &mode_auto &&
9 2666 : AP_Mission::is_nav_cmd(cmd) &&
10 1237 : mission.get_prev_nav_cmd_index() != cmd.index) {
11 1065 : reset_alt_offset();
12 : }
13 :
14 : // default to non-VTOL loiter
15 1337 : auto_state.vtol_loiter = false;
16 :
17 : #if AP_TERRAIN_AVAILABLE
18 1337 : plane.target_altitude.terrain_following_pending = false;
19 : #endif
20 :
21 : // special handling for nav vs non-nav commands
22 1337 : if (AP_Mission::is_nav_cmd(cmd)) {
23 : // set takeoff_complete to true so we don't add extra elevator
24 : // except in a takeoff
25 1245 : auto_state.takeoff_complete = true;
26 :
27 1245 : nav_controller->set_data_is_stale();
28 :
29 : // start non-idle
30 1245 : auto_state.idle_mode = false;
31 :
32 : // reset loiter start time. New command is a new loiter
33 1245 : loiter.start_time_ms = 0;
34 :
35 : // Mission lookahead is only valid in auto
36 1245 : if (control_mode == &mode_auto) {
37 1237 : AP_Mission::Mission_Command next_nav_cmd;
38 1237 : const uint16_t next_index = mission.get_current_nav_index() + 1;
39 1237 : const bool have_next_cmd = mission.get_next_nav_cmd(next_index, next_nav_cmd);
40 1237 : auto_state.wp_is_land_approach = have_next_cmd && (next_nav_cmd.id == MAV_CMD_NAV_LAND);
41 : #if HAL_QUADPLANE_ENABLED
42 1237 : if (have_next_cmd && quadplane.is_vtol_land(next_nav_cmd.id)) {
43 34 : auto_state.wp_is_land_approach = false;
44 : }
45 : #endif
46 : }
47 : }
48 :
49 1337 : switch(cmd.id) {
50 :
51 153 : case MAV_CMD_NAV_TAKEOFF:
52 153 : crash_state.is_crashed = false;
53 : #if HAL_QUADPLANE_ENABLED
54 153 : if (quadplane.is_vtol_takeoff(cmd.id)) {
55 13 : return quadplane.do_vtol_takeoff(cmd);
56 : }
57 : #endif
58 140 : do_takeoff(cmd);
59 140 : break;
60 :
61 876 : case MAV_CMD_NAV_WAYPOINT: // Navigate to Waypoint
62 876 : do_nav_wp(cmd);
63 876 : break;
64 :
65 116 : case MAV_CMD_NAV_LAND: // LAND to Waypoint
66 : #if HAL_QUADPLANE_ENABLED
67 116 : if (quadplane.is_vtol_land(cmd.id)) {
68 2 : crash_state.is_crashed = false;
69 2 : return quadplane.do_vtol_land(cmd);
70 : }
71 : #endif
72 114 : do_land(cmd);
73 114 : break;
74 :
75 7 : case MAV_CMD_NAV_LOITER_UNLIM: // Loiter indefinitely
76 7 : do_loiter_unlimited(cmd);
77 7 : break;
78 :
79 6 : case MAV_CMD_NAV_LOITER_TURNS: // Loiter N Times
80 6 : do_loiter_turns(cmd);
81 6 : break;
82 :
83 1 : case MAV_CMD_NAV_LOITER_TIME:
84 1 : do_loiter_time(cmd);
85 1 : break;
86 :
87 16 : case MAV_CMD_NAV_LOITER_TO_ALT:
88 16 : do_loiter_to_alt(cmd);
89 16 : break;
90 :
91 3 : case MAV_CMD_NAV_RETURN_TO_LAUNCH:
92 3 : set_mode(mode_rtl, ModeReason::MISSION_CMD);
93 3 : break;
94 :
95 0 : case MAV_CMD_NAV_CONTINUE_AND_CHANGE_ALT:
96 0 : do_continue_and_change_alt(cmd);
97 0 : break;
98 :
99 2 : case MAV_CMD_NAV_ALTITUDE_WAIT:
100 2 : do_altitude_wait(cmd);
101 2 : break;
102 :
103 : #if HAL_QUADPLANE_ENABLED
104 36 : case MAV_CMD_NAV_VTOL_TAKEOFF:
105 36 : crash_state.is_crashed = false;
106 36 : return quadplane.do_vtol_takeoff(cmd);
107 :
108 29 : case MAV_CMD_NAV_VTOL_LAND:
109 : case MAV_CMD_NAV_PAYLOAD_PLACE:
110 29 : if (quadplane.landing_with_fixed_wing_spiral_approach()) {
111 : // the user wants to approach the landing in a fixed wing flight mode
112 : // the waypoint will be used as a loiter_to_alt
113 : // after which point the plane will compute the optimal into the wind direction
114 : // and fly in on that direction towards the landing waypoint
115 : // it will then transition to VTOL and do a normal quadplane landing
116 2 : do_landing_vtol_approach(cmd);
117 2 : break;
118 : } else {
119 27 : return quadplane.do_vtol_land(cmd);
120 : }
121 : #endif
122 :
123 : // Conditional commands
124 :
125 0 : case MAV_CMD_CONDITION_DELAY:
126 0 : do_wait_delay(cmd);
127 0 : break;
128 :
129 0 : case MAV_CMD_CONDITION_DISTANCE:
130 0 : do_within_distance(cmd);
131 0 : break;
132 :
133 : // Do commands
134 :
135 11 : case MAV_CMD_DO_CHANGE_SPEED:
136 11 : do_change_speed(cmd);
137 11 : break;
138 :
139 0 : case MAV_CMD_DO_SET_HOME:
140 0 : do_set_home(cmd);
141 0 : break;
142 :
143 2 : case MAV_CMD_DO_INVERTED_FLIGHT:
144 2 : if (cmd.p1 == 0 || cmd.p1 == 1) {
145 2 : auto_state.inverted_flight = (bool)cmd.p1;
146 2 : gcs().send_text(MAV_SEVERITY_INFO, "Set inverted %u", cmd.p1);
147 : }
148 2 : break;
149 :
150 55 : case MAV_CMD_DO_RETURN_PATH_START:
151 : case MAV_CMD_DO_LAND_START:
152 55 : break;
153 :
154 0 : case MAV_CMD_DO_AUTOTUNE_ENABLE:
155 0 : autotune_enable(cmd.p1);
156 0 : break;
157 :
158 : #if HAL_MOUNT_ENABLED
159 : // Sets the region of interest (ROI) for a sensor set or the
160 : // vehicle itself. This can then be used by the vehicles control
161 : // system to control the vehicle attitude and the attitude of various
162 : // devices such as cameras.
163 : // |Region of interest mode. (see MAV_ROI enum)| Waypoint index/ target ID. (see MAV_ROI enum)| ROI index (allows a vehicle to manage multiple cameras etc.)| Empty| x the location of the fixed ROI (see MAV_FRAME)| y| z|
164 : // ROI_NONE can be handled by the regular ROI handler because lat, lon, alt are always zero
165 0 : case MAV_CMD_DO_SET_ROI_LOCATION:
166 : case MAV_CMD_DO_SET_ROI_NONE:
167 : case MAV_CMD_DO_SET_ROI:
168 0 : if (!cmd.content.location.initialised()) {
169 : // switch off the camera tracking if enabled
170 0 : if (camera_mount.get_mode() == MAV_MOUNT_MODE_GPS_POINT) {
171 0 : camera_mount.set_mode_to_default();
172 : }
173 : } else {
174 : // set mount's target location
175 0 : camera_mount.set_roi_target(cmd.content.location);
176 : }
177 0 : break;
178 :
179 0 : case MAV_CMD_DO_MOUNT_CONTROL: // 205
180 : // point the camera to a specified angle
181 0 : camera_mount.set_angle_target(cmd.content.mount_control.roll, cmd.content.mount_control.pitch, cmd.content.mount_control.yaw, false);
182 0 : break;
183 : #endif
184 :
185 : #if HAL_QUADPLANE_ENABLED
186 2 : case MAV_CMD_DO_VTOL_TRANSITION:
187 2 : plane.quadplane.handle_do_vtol_transition((enum MAV_VTOL_STATE)cmd.content.do_vtol_transition.target_state);
188 2 : break;
189 : #endif
190 :
191 : #if AP_ICENGINE_ENABLED
192 21 : case MAV_CMD_DO_ENGINE_CONTROL:
193 21 : plane.g2.ice_control.engine_control(cmd.content.do_engine_control.start_control,
194 21 : cmd.content.do_engine_control.cold_start,
195 21 : cmd.content.do_engine_control.height_delay_cm*0.01f,
196 21 : cmd.content.do_engine_control.allow_disarmed_start);
197 21 : break;
198 : #endif
199 :
200 : #if AP_SCRIPTING_ENABLED
201 0 : case MAV_CMD_NAV_SCRIPT_TIME:
202 0 : do_nav_script_time(cmd);
203 0 : break;
204 : #endif
205 :
206 0 : case MAV_CMD_NAV_DELAY:
207 0 : mode_auto.do_nav_delay(cmd);
208 0 : break;
209 :
210 1 : default:
211 : // unable to use the command, allow the vehicle to try the next command
212 1 : return false;
213 : }
214 :
215 1258 : return true;
216 : }
217 :
218 : /*******************************************************************************
219 : Verify command Handlers
220 :
221 : Each type of mission element has a "verify" operation. The verify
222 : operation returns true when the mission element has completed and we
223 : should move onto the next mission element.
224 : Return true if we do not recognize the command so that we move on to the next command
225 : *******************************************************************************/
226 :
227 246732 : bool Plane::verify_command(const AP_Mission::Mission_Command& cmd) // Returns true if command complete
228 : {
229 246732 : switch(cmd.id) {
230 :
231 8792 : case MAV_CMD_NAV_TAKEOFF:
232 : #if HAL_QUADPLANE_ENABLED
233 8792 : if (quadplane.is_vtol_takeoff(cmd.id)) {
234 3725 : return quadplane.verify_vtol_takeoff(cmd);
235 : }
236 : #endif
237 5067 : return verify_takeoff();
238 :
239 145036 : case MAV_CMD_NAV_WAYPOINT:
240 145036 : return verify_nav_wp(cmd);
241 :
242 40526 : case MAV_CMD_NAV_LAND:
243 : #if HAL_QUADPLANE_ENABLED
244 40526 : if (quadplane.is_vtol_land(cmd.id)) {
245 1393 : return quadplane.verify_vtol_land();
246 : }
247 : #endif
248 39133 : if (flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING) {
249 247 : return landing.verify_abort_landing(prev_WP_loc, next_WP_loc, current_loc, auto_state.takeoff_altitude_rel_cm, throttle_suppressed);
250 :
251 : } else {
252 : // use rangefinder to correct if possible
253 38886 : bool rangefinder_active = false;
254 38886 : float height = plane.get_landing_height(rangefinder_active);
255 :
256 : // for flare calculations we don't want to use the terrain
257 : // correction as otherwise we will flare early on rising
258 : // ground
259 38886 : height -= auto_state.terrain_correction;
260 38886 : return landing.verify_land(prev_WP_loc, next_WP_loc, current_loc,
261 38886 : height, auto_state.sink_rate, auto_state.wp_proportion, auto_state.last_flying_ms, arming.is_armed(), is_flying(),
262 38886 : rangefinder_active);
263 : }
264 :
265 476 : case MAV_CMD_NAV_LOITER_UNLIM:
266 476 : return verify_loiter_unlim(cmd);
267 :
268 2951 : case MAV_CMD_NAV_LOITER_TURNS:
269 2951 : return verify_loiter_turns(cmd);
270 :
271 2880 : case MAV_CMD_NAV_LOITER_TIME:
272 2880 : return verify_loiter_time();
273 :
274 8707 : case MAV_CMD_NAV_LOITER_TO_ALT:
275 8707 : return verify_loiter_to_alt(cmd);
276 :
277 :
278 0 : case MAV_CMD_NAV_CONTINUE_AND_CHANGE_ALT:
279 0 : return verify_continue_and_change_alt();
280 :
281 10478 : case MAV_CMD_NAV_ALTITUDE_WAIT:
282 10478 : return mode_auto.verify_altitude_wait(cmd);
283 :
284 : #if HAL_QUADPLANE_ENABLED
285 6001 : case MAV_CMD_NAV_VTOL_TAKEOFF:
286 6001 : return quadplane.verify_vtol_takeoff(cmd);
287 20795 : case MAV_CMD_NAV_VTOL_LAND:
288 : case MAV_CMD_NAV_PAYLOAD_PLACE:
289 20795 : if (quadplane.landing_with_fixed_wing_spiral_approach() && !verify_landing_vtol_approach(cmd)) {
290 : // verify_landing_vtol_approach will return true once we have completed the approach,
291 : // in which case we fall over to normal vtol landing code
292 1026 : return false;
293 : } else {
294 19769 : return quadplane.verify_vtol_land();
295 : }
296 : #endif // HAL_QUADPLANE_ENABLED
297 :
298 : // Conditional commands
299 :
300 0 : case MAV_CMD_CONDITION_DELAY:
301 0 : return verify_wait_delay();
302 :
303 0 : case MAV_CMD_CONDITION_DISTANCE:
304 0 : return verify_within_distance();
305 :
306 : #if AP_SCRIPTING_ENABLED
307 0 : case MAV_CMD_NAV_SCRIPT_TIME:
308 0 : return verify_nav_script_time(cmd);
309 : #endif
310 :
311 0 : case MAV_CMD_NAV_DELAY:
312 0 : return mode_auto.verify_nav_delay(cmd);
313 :
314 : // do commands (always return true)
315 90 : case MAV_CMD_DO_CHANGE_SPEED:
316 : case MAV_CMD_DO_SET_HOME:
317 : case MAV_CMD_DO_INVERTED_FLIGHT:
318 : case MAV_CMD_DO_RETURN_PATH_START:
319 : case MAV_CMD_DO_LAND_START:
320 : case MAV_CMD_DO_FENCE_ENABLE:
321 : case MAV_CMD_DO_AUTOTUNE_ENABLE:
322 : case MAV_CMD_DO_SET_CAM_TRIGG_DIST:
323 : case MAV_CMD_DO_SET_ROI_LOCATION:
324 : case MAV_CMD_DO_SET_ROI_NONE:
325 : case MAV_CMD_DO_SET_ROI:
326 : case MAV_CMD_DO_MOUNT_CONTROL:
327 : case MAV_CMD_DO_VTOL_TRANSITION:
328 : case MAV_CMD_DO_ENGINE_CONTROL:
329 90 : return true;
330 :
331 0 : default:
332 : // error message
333 0 : gcs().send_text(MAV_SEVERITY_WARNING,"Skipping invalid cmd #%i",cmd.id);
334 : // return true if we do not recognize the command so that we move on to the next command
335 0 : return true;
336 : }
337 : }
338 :
339 : /********************************************************************************/
340 : // Nav (Must) commands
341 : /********************************************************************************/
342 :
343 87 : void Plane::do_RTL(int32_t rtl_altitude_AMSL_cm)
344 : {
345 87 : auto_state.next_wp_crosstrack = false;
346 87 : auto_state.crosstrack = false;
347 87 : prev_WP_loc = current_loc;
348 87 : next_WP_loc = calc_best_rally_or_home_location(current_loc, rtl_altitude_AMSL_cm);
349 :
350 87 : fix_terrain_WP(next_WP_loc, __AP_LINE__);
351 :
352 87 : setup_terrain_target_alt(next_WP_loc);
353 87 : set_target_altitude_location(next_WP_loc);
354 :
355 87 : if (aparm.loiter_radius < 0) {
356 0 : loiter.direction = -1;
357 : } else {
358 87 : loiter.direction = 1;
359 : }
360 :
361 87 : setup_alt_slope();
362 87 : setup_turn_angle();
363 87 : }
364 :
365 98 : Location Plane::calc_best_rally_or_home_location(const Location &_current_loc, float rtl_home_alt_amsl_cm) const
366 : {
367 : #if HAL_RALLY_ENABLED
368 98 : return plane.rally.calc_best_rally_or_home_location(_current_loc, rtl_home_alt_amsl_cm);
369 : #else
370 : return Location {
371 : plane.home.lat,
372 : plane.home.lng,
373 : int32_t(rtl_home_alt_amsl_cm),
374 : Location::AltFrame::ABSOLUTE
375 : };
376 : #endif
377 : }
378 :
379 : /*
380 : start a NAV_TAKEOFF command
381 : */
382 140 : void Plane::do_takeoff(const AP_Mission::Mission_Command& cmd)
383 : {
384 140 : prev_WP_loc = current_loc;
385 140 : set_next_WP(cmd.content.location);
386 : // pitch in deg, airspeed m/s, throttle %, track WP 1 or 0
387 140 : auto_state.takeoff_pitch_cd = (int16_t)cmd.p1 * 100;
388 140 : if (auto_state.takeoff_pitch_cd <= 0) {
389 : // if the mission doesn't specify a pitch use 4 degrees
390 27 : auto_state.takeoff_pitch_cd = 400;
391 : }
392 140 : auto_state.takeoff_altitude_rel_cm = next_WP_loc.alt - home.alt;
393 140 : next_WP_loc.lat = home.lat + 10;
394 140 : next_WP_loc.lng = home.lng + 10;
395 140 : auto_state.takeoff_complete = false; // set flag to use gps ground course during TO. IMU will be doing yaw drift correction.
396 140 : auto_state.rotation_complete = false;
397 140 : auto_state.height_below_takeoff_to_level_off_cm = 0;
398 : // Flag also used to override "on the ground" throttle disable
399 :
400 : // zero locked course
401 140 : steer_state.locked_course_err = 0;
402 140 : steer_state.hold_course_cd = -1;
403 140 : auto_state.baro_takeoff_alt = barometer.get_altitude();
404 140 : }
405 :
406 876 : void Plane::do_nav_wp(const AP_Mission::Mission_Command& cmd)
407 : {
408 876 : set_next_WP(cmd.content.location);
409 876 : }
410 :
411 114 : void Plane::do_land(const AP_Mission::Mission_Command& cmd)
412 : {
413 114 : set_next_WP(cmd.content.location);
414 :
415 : // configure abort altitude and pitch
416 : // if NAV_LAND has an abort altitude then use it, else use last takeoff, else use 30m
417 114 : if (cmd.p1 > 0) {
418 1 : auto_state.takeoff_altitude_rel_cm = (int16_t)cmd.p1 * 100;
419 113 : } else if (auto_state.takeoff_altitude_rel_cm <= 0) {
420 1 : auto_state.takeoff_altitude_rel_cm = 3000;
421 : }
422 :
423 114 : if (auto_state.takeoff_pitch_cd <= 0) {
424 : // If no takeoff command has ever been used, default to a conservative 10deg
425 1 : auto_state.takeoff_pitch_cd = 1000;
426 : }
427 :
428 : #if AP_RANGEFINDER_ENABLED
429 : // zero rangefinder state, start to accumulate good samples now
430 114 : memset(&rangefinder_state, 0, sizeof(rangefinder_state));
431 : #endif
432 :
433 114 : landing.do_land(cmd, relative_altitude);
434 :
435 114 : if (flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING) {
436 : // if we were in an abort we need to explicitly move out of the abort state, as it's sticky
437 0 : set_flight_stage(AP_FixedWing::FlightStage::LAND);
438 : }
439 114 : }
440 :
441 : #if HAL_QUADPLANE_ENABLED
442 2 : void Plane::do_landing_vtol_approach(const AP_Mission::Mission_Command& cmd)
443 : {
444 : //set target alt
445 2 : Location loc = cmd.content.location;
446 2 : loc.sanitize(current_loc);
447 2 : set_next_WP(loc);
448 :
449 2 : vtol_approach_s.approach_stage = VTOLApproach::Stage::LOITER_TO_ALT;
450 2 : }
451 : #endif
452 :
453 30 : void Plane::loiter_set_direction_wp(const AP_Mission::Mission_Command& cmd)
454 : {
455 30 : if (cmd.content.location.loiter_ccw) {
456 4 : loiter.direction = -1;
457 : } else {
458 26 : loiter.direction = 1;
459 : }
460 30 : }
461 :
462 7 : void Plane::do_loiter_unlimited(const AP_Mission::Mission_Command& cmd)
463 : {
464 7 : Location cmdloc = cmd.content.location;
465 7 : cmdloc.sanitize(current_loc);
466 7 : set_next_WP(cmdloc);
467 7 : loiter_set_direction_wp(cmd);
468 7 : }
469 :
470 6 : void Plane::do_loiter_turns(const AP_Mission::Mission_Command& cmd)
471 : {
472 6 : Location cmdloc = cmd.content.location;
473 6 : cmdloc.sanitize(current_loc);
474 6 : set_next_WP(cmdloc);
475 6 : loiter_set_direction_wp(cmd);
476 6 : const float turns = cmd.get_loiter_turns();
477 :
478 6 : loiter.total_cd = (uint32_t)(turns * 36000UL);
479 6 : condition_value = 1; // used to signify primary turns goal not yet met
480 6 : }
481 :
482 1 : void Plane::do_loiter_time(const AP_Mission::Mission_Command& cmd)
483 : {
484 1 : Location cmdloc = cmd.content.location;
485 1 : cmdloc.sanitize(current_loc);
486 1 : set_next_WP(cmdloc);
487 1 : loiter_set_direction_wp(cmd);
488 :
489 : // we set start_time_ms when we reach the waypoint
490 1 : loiter.time_max_ms = cmd.p1 * (uint32_t)1000; // convert sec to ms
491 1 : condition_value = 1; // used to signify primary time goal not yet met
492 1 : }
493 :
494 0 : void Plane::do_continue_and_change_alt(const AP_Mission::Mission_Command& cmd)
495 : {
496 : // select heading method. Either mission, gps bearing projection or yaw based
497 : // If prev_WP_loc and next_WP_loc are different then an accurate wp based bearing can
498 : // be computed. However, if we had just changed modes before this, such as an aborted landing
499 : // via mode change, the prev and next wps are the same.
500 : float bearing;
501 0 : if (!prev_WP_loc.same_latlon_as(next_WP_loc)) {
502 : // use waypoint based bearing, this is the usual case
503 0 : steer_state.hold_course_cd = -1;
504 0 : } else if (AP::gps().status() >= AP_GPS_FixType::FIX_2D) {
505 : // use gps ground course based bearing hold
506 0 : steer_state.hold_course_cd = -1;
507 0 : bearing = AP::gps().ground_course();
508 0 : next_WP_loc.offset_bearing(bearing, 1000); // push it out 1km
509 : } else {
510 : // use yaw based bearing hold
511 0 : steer_state.hold_course_cd = wrap_360_cd(ahrs.yaw_sensor);
512 0 : bearing = ahrs.get_yaw_deg();
513 0 : next_WP_loc.offset_bearing(bearing, 1000); // push it out 1km
514 : }
515 :
516 0 : if (cmd.content.location.get_alt_frame() == Location::AltFrame::ABOVE_TERRAIN) {
517 0 : next_WP_loc.copy_alt_from(cmd.content.location);
518 : } else {
519 : int32_t alt_abs_cm;
520 : // if this fails we don't change alt
521 0 : if (cmd.content.location.get_alt_cm(Location::AltFrame::ABSOLUTE, alt_abs_cm)) {
522 0 : next_WP_loc.set_alt_cm(alt_abs_cm,
523 : Location::AltFrame::ABSOLUTE);
524 : }
525 : }
526 0 : condition_value = cmd.p1;
527 0 : reset_offset_altitude();
528 0 : }
529 :
530 2 : void Plane::do_altitude_wait(const AP_Mission::Mission_Command& cmd)
531 : {
532 : // set all servos to trim until we reach altitude or descent speed
533 2 : auto_state.idle_mode = true;
534 : #if AP_PLANE_GLIDER_PULLUP_ENABLED
535 2 : mode_auto.pullup.reset();
536 : #endif
537 2 : }
538 :
539 16 : void Plane::do_loiter_to_alt(const AP_Mission::Mission_Command& cmd)
540 : {
541 : //set target alt
542 16 : Location loc = cmd.content.location;
543 16 : loc.sanitize(current_loc);
544 16 : set_next_WP(loc);
545 16 : loiter_set_direction_wp(cmd);
546 :
547 : // init to 0, set to 1 when altitude is reached
548 16 : condition_value = 0;
549 16 : }
550 :
551 : // do_nav_delay - Delay the next navigation command
552 0 : void ModeAuto::do_nav_delay(const AP_Mission::Mission_Command& cmd)
553 : {
554 0 : nav_delay.time_start_ms = millis();
555 :
556 0 : if (cmd.content.nav_delay.seconds > 0) {
557 : // relative delay
558 0 : nav_delay.time_max_ms = cmd.content.nav_delay.seconds * 1000; // convert seconds to milliseconds
559 : } else {
560 : // absolute delay to utc time
561 : #if AP_RTC_ENABLED
562 0 : nav_delay.time_max_ms = AP::rtc().get_time_utc(cmd.content.nav_delay.hour_utc, cmd.content.nav_delay.min_utc, cmd.content.nav_delay.sec_utc, 0);
563 : #else
564 : nav_delay.time_max_ms = 0;
565 : #endif
566 : }
567 0 : gcs().send_text(MAV_SEVERITY_INFO, "Delaying %u sec", (unsigned)(nav_delay.time_max_ms/1000));
568 0 : }
569 :
570 : /********************************************************************************/
571 : // Verify Nav (Must) commands
572 : /********************************************************************************/
573 5067 : bool Plane::verify_takeoff()
574 : {
575 5067 : bool trust_ahrs_yaw = AP::ahrs().initialised();
576 : #if AP_AHRS_DCM_ENABLED
577 5067 : trust_ahrs_yaw |= ahrs.dcm_yaw_initialised();
578 : #endif
579 5067 : if (trust_ahrs_yaw && steer_state.hold_course_cd == -1) {
580 : // once we reach sufficient speed for good GPS course
581 : // estimation we save our current GPS ground course
582 : // corrected for summed yaw to set the take off
583 : // course. This keeps wings level until we are ready to
584 : // rotate, and also allows us to cope with arbitrary
585 : // compass errors for auto takeoff
586 3924 : if (gps.status() >= AP_GPS_FixType::FIX_3D &&
587 3924 : gps.ground_speed() > GPS_GND_CRS_MIN_SPD &&
588 135 : hal.util->safety_switch_state() != AP_HAL::Util::SAFETY_DISARMED) {
589 135 : float takeoff_course = wrap_PI(radians(gps.ground_course())) - steer_state.locked_course_err;
590 135 : takeoff_course = wrap_PI(takeoff_course);
591 135 : steer_state.hold_course_cd = wrap_360_cd(degrees(takeoff_course)*100);
592 135 : gcs().send_text(MAV_SEVERITY_INFO, "Holding course %d at %.1fm/s (%.1f)",
593 135 : (int)steer_state.hold_course_cd,
594 135 : (double)gps.ground_speed(),
595 135 : (double)degrees(steer_state.locked_course_err));
596 : }
597 : }
598 :
599 5067 : if (steer_state.hold_course_cd != -1) {
600 : // call navigation controller for heading hold
601 3240 : nav_controller->update_heading_hold(steer_state.hold_course_cd);
602 : } else {
603 1827 : nav_controller->update_level_flight();
604 : }
605 :
606 : // check for optional takeoff timeout
607 5067 : if (plane.check_takeoff_timeout()) {
608 0 : mission.reset();
609 : }
610 :
611 : // see if we have reached takeoff altitude
612 5067 : int32_t relative_alt_cm = adjusted_relative_altitude_cm();
613 5067 : if (
614 10015 : relative_alt_cm > auto_state.takeoff_altitude_rel_cm || // altitude reached
615 4948 : plane.check_takeoff_timeout_level_off() // pitch level-off maneuver has timed out
616 : ) {
617 130 : gcs().send_text(MAV_SEVERITY_INFO, "Takeoff complete at %.2fm",
618 130 : (double)(relative_alt_cm*0.01f));
619 130 : steer_state.hold_course_cd = -1;
620 130 : auto_state.takeoff_complete = true;
621 130 : next_WP_loc = prev_WP_loc = current_loc;
622 :
623 : #if AP_FENCE_ENABLED
624 130 : plane.fence.auto_enable_fence_after_takeoff();
625 : #endif
626 :
627 : // don't cross-track on completion of takeoff, as otherwise we
628 : // can end up doing too sharp a turn
629 130 : auto_state.next_wp_crosstrack = false;
630 130 : return true;
631 : } else {
632 4937 : return false;
633 : }
634 : }
635 :
636 : /*
637 : update navigation for normal mission waypoints. Return true when the
638 : waypoint is complete
639 : */
640 145036 : bool Plane::verify_nav_wp(const AP_Mission::Mission_Command& cmd)
641 : {
642 145036 : steer_state.hold_course_cd = -1;
643 :
644 : // depending on the pass by flag either go to waypoint in regular manner or
645 : // fly past it for set distance along the line of waypoints
646 145036 : Location flex_next_WP_loc = next_WP_loc;
647 :
648 145036 : uint8_t cmd_passby = HIGHBYTE(cmd.p1); // distance in meters to pass beyond the wp
649 145036 : uint8_t cmd_acceptance_distance = LOWBYTE(cmd.p1); // radius in meters to accept reaching the wp
650 :
651 145036 : if (cmd_passby > 0) {
652 0 : const float dist = prev_WP_loc.get_distance(flex_next_WP_loc);
653 0 : const float bearing_deg = degrees(prev_WP_loc.get_bearing(flex_next_WP_loc));
654 :
655 0 : if (is_positive(dist)) {
656 0 : flex_next_WP_loc.offset_bearing(bearing_deg, cmd_passby);
657 : }
658 : }
659 :
660 145036 : if (auto_state.crosstrack) {
661 92605 : nav_controller->update_waypoint(prev_WP_loc, flex_next_WP_loc);
662 : } else {
663 52431 : nav_controller->update_waypoint(current_loc, flex_next_WP_loc);
664 : }
665 :
666 : // see if the user has specified a maximum distance to waypoint
667 : // If override with p3 - then this is not used as it will overfly badly
668 145036 : if (g.waypoint_max_radius > 0 &&
669 0 : auto_state.wp_distance > (uint16_t)g.waypoint_max_radius) {
670 0 : if (current_loc.past_interval_finish_line(prev_WP_loc, flex_next_WP_loc)) {
671 : // this is needed to ensure completion of the waypoint
672 0 : if (cmd_passby == 0) {
673 0 : prev_WP_loc = current_loc;
674 : }
675 : }
676 0 : return false;
677 : }
678 :
679 145036 : float acceptance_distance_m = 0; // default to: if overflown - let it fly up to the point
680 145036 : if (cmd_acceptance_distance > 0) {
681 : // allow user to override acceptance radius
682 0 : acceptance_distance_m = cmd_acceptance_distance;
683 145036 : } else if (cmd_passby == 0) {
684 145036 : acceptance_distance_m = nav_controller->turn_distance(get_wp_radius(), auto_state.next_turn_angle);
685 : }
686 145036 : const float wp_dist = current_loc.get_distance(flex_next_WP_loc);
687 145036 : if (wp_dist <= acceptance_distance_m) {
688 704 : gcs().send_text(MAV_SEVERITY_INFO, "Reached waypoint #%i dist %um",
689 704 : (unsigned)mission.get_current_nav_cmd().index,
690 704 : (unsigned)current_loc.get_distance(flex_next_WP_loc));
691 704 : return true;
692 : }
693 :
694 : // have we flown past the waypoint?
695 144332 : if (current_loc.past_interval_finish_line(prev_WP_loc, flex_next_WP_loc)) {
696 48 : gcs().send_text(MAV_SEVERITY_INFO, "Passed waypoint #%i dist %um",
697 48 : (unsigned)mission.get_current_nav_cmd().index,
698 48 : (unsigned)current_loc.get_distance(flex_next_WP_loc));
699 48 : return true;
700 : }
701 :
702 144284 : return false;
703 : }
704 :
705 476 : bool Plane::verify_loiter_unlim(const AP_Mission::Mission_Command &cmd)
706 : {
707 : // else use mission radius
708 476 : update_loiter(cmd.p1);
709 476 : return false;
710 : }
711 :
712 2880 : bool Plane::verify_loiter_time()
713 : {
714 2880 : bool result = false;
715 : // mission radius is always aparm.loiter_radius
716 2880 : update_loiter(0);
717 :
718 2880 : if (loiter.start_time_ms == 0) {
719 2830 : if (reached_loiter_target() && loiter.sum_cd > 1) {
720 : // we've reached the target, start the timer
721 0 : loiter.start_time_ms = millis();
722 : }
723 50 : } else if (condition_value != 0) {
724 : // primary goal, loiter time
725 50 : if ((millis() - loiter.start_time_ms) > loiter.time_max_ms) {
726 : // primary goal completed, initialize secondary heading goal
727 0 : condition_value = 0;
728 0 : result = verify_loiter_heading(true);
729 : }
730 : } else {
731 : // secondary goal, loiter to heading
732 0 : result = verify_loiter_heading(false);
733 : }
734 :
735 2880 : if (result) {
736 0 : gcs().send_text(MAV_SEVERITY_INFO,"Loiter time complete");
737 0 : auto_state.vtol_loiter = false;
738 : }
739 2880 : return result;
740 : }
741 :
742 2951 : bool Plane::verify_loiter_turns(const AP_Mission::Mission_Command &cmd)
743 : {
744 2951 : bool result = false;
745 2951 : uint16_t radius = HIGHBYTE(cmd.p1);
746 2951 : if (cmd.type_specific_bits & (1U<<0)) {
747 : // special storage handling allows for larger radii
748 479 : radius *= 10;
749 : }
750 2951 : update_loiter(radius);
751 :
752 : // LOITER_TURNS makes no sense as VTOL
753 2951 : auto_state.vtol_loiter = false;
754 :
755 2951 : if (!reached_loiter_target()) {
756 1498 : result = false;
757 1453 : } else if (condition_value != 0) {
758 : // primary goal, loiter turns
759 604 : if (loiter.sum_cd > loiter.total_cd && loiter.sum_cd > 1) {
760 : // primary goal completed, initialize secondary heading goal
761 5 : condition_value = 0;
762 5 : result = verify_loiter_heading(true);
763 : }
764 : } else {
765 : // secondary goal, loiter to heading
766 849 : result = verify_loiter_heading(false);
767 : }
768 :
769 2951 : if (result) {
770 4 : gcs().send_text(MAV_SEVERITY_INFO,"Loiter orbits complete");
771 : }
772 2951 : return result;
773 : }
774 :
775 : /*
776 : verify a LOITER_TO_ALT command. This involves checking we have
777 : reached both the desired altitude and desired heading. The desired
778 : altitude only needs to be reached once.
779 : */
780 12667 : bool Plane::verify_loiter_to_alt(const AP_Mission::Mission_Command &cmd)
781 : {
782 12667 : bool result = false;
783 :
784 12667 : update_loiter(cmd.p1);
785 :
786 : // condition_value == 0 means alt has never been reached
787 12667 : if (condition_value == 0) {
788 : // primary goal, loiter to alt
789 10655 : if (labs(loiter.sum_cd) > 1 && (loiter.reached_target_alt || loiter.unable_to_achieve_target_alt)) {
790 : // primary goal completed, initialize secondary heading goal
791 13 : if (loiter.unable_to_achieve_target_alt) {
792 0 : gcs().send_text(MAV_SEVERITY_INFO,"Loiter to alt was stuck at %d", int(current_loc.alt/100));
793 : }
794 :
795 13 : condition_value = 1;
796 13 : result = verify_loiter_heading(true);
797 : }
798 : } else {
799 : // secondary goal, loiter to heading
800 2012 : result = verify_loiter_heading(false);
801 : }
802 :
803 12667 : if (result) {
804 13 : gcs().send_text(MAV_SEVERITY_INFO,"Loiter to alt complete");
805 : }
806 12667 : return result;
807 : }
808 :
809 :
810 0 : bool Plane::verify_continue_and_change_alt()
811 : {
812 : // is waypoint info not available and heading hold is?
813 0 : if (prev_WP_loc.same_latlon_as(next_WP_loc) &&
814 0 : steer_state.hold_course_cd != -1) {
815 : //keep flying the same course with fixed steering heading computed at start if cmd
816 0 : nav_controller->update_heading_hold(steer_state.hold_course_cd);
817 : }
818 : else {
819 : // Is the next_WP less than 200 m away?
820 0 : if (current_loc.get_distance(next_WP_loc) < 200.0f) {
821 : //push another 300 m down the line
822 0 : int32_t next_wp_bearing_cd = prev_WP_loc.get_bearing_to(next_WP_loc);
823 0 : next_WP_loc.offset_bearing(next_wp_bearing_cd * 0.01f, 300.0f);
824 : }
825 :
826 : //keep flying the same course
827 0 : nav_controller->update_waypoint(prev_WP_loc, next_WP_loc);
828 : }
829 :
830 : //climbing?
831 0 : if (condition_value == 1 && adjusted_altitude_cm() >= next_WP_loc.alt) {
832 0 : return true;
833 : }
834 : //descending?
835 0 : else if (condition_value == 2 &&
836 0 : adjusted_altitude_cm() <= next_WP_loc.alt) {
837 0 : return true;
838 : }
839 : //don't care if we're climbing or descending
840 0 : else if (labs(adjusted_altitude_cm() - next_WP_loc.alt) <= 500) {
841 0 : return true;
842 : }
843 :
844 0 : return false;
845 : }
846 :
847 : /*
848 : see if we have reached altitude or descent speed
849 : */
850 10478 : bool ModeAuto::verify_altitude_wait(const AP_Mission::Mission_Command &cmd)
851 : {
852 : #if AP_PLANE_GLIDER_PULLUP_ENABLED
853 10478 : if (pullup.in_pullup()) {
854 81 : return pullup.verify_pullup();
855 : }
856 : #endif
857 :
858 : /*
859 : the target altitude in param1 is always AMSL
860 : */
861 10397 : const float alt_diff = plane.current_loc.alt*0.01 - cmd.content.altitude_wait.altitude;
862 10397 : bool completed = false;
863 10397 : if (alt_diff > 0) {
864 1 : gcs().send_text(MAV_SEVERITY_INFO,"Reached altitude");
865 1 : completed = true;
866 10396 : } else if (cmd.content.altitude_wait.descent_rate > 0 &&
867 10396 : plane.auto_state.sink_rate > cmd.content.altitude_wait.descent_rate) {
868 0 : gcs().send_text(MAV_SEVERITY_INFO, "Reached descent rate %.1f m/s", (double)plane.auto_state.sink_rate);
869 0 : completed = true;
870 : }
871 :
872 10397 : if (completed) {
873 : #if AP_PLANE_GLIDER_PULLUP_ENABLED
874 1 : if (pullup.pullup_start()) {
875 : // we are doing a pullup, ALTITUDE_WAIT not complete until pullup is done
876 1 : return false;
877 : }
878 : #endif
879 0 : return true;
880 : }
881 :
882 10396 : const float time_to_alt = alt_diff / MIN(plane.auto_state.sink_rate, -0.01);
883 :
884 : /*
885 : if requested, wiggle servos
886 :
887 : we don't start a wiggle if we expect to release soon as we don't
888 : want the servos to be off trim at the time of release
889 : */
890 10396 : if (cmd.content.altitude_wait.wiggle_time != 0 &&
891 10396 : (plane.auto_state.sink_rate > 0 || time_to_alt > cmd.content.altitude_wait.wiggle_time*5)) {
892 17276 : if (wiggle.stage == 0 &&
893 8380 : AP_HAL::millis() - wiggle.last_ms > cmd.content.altitude_wait.wiggle_time*1000) {
894 34 : wiggle.stage = 1;
895 34 : wiggle.last_ms = AP_HAL::millis();
896 : // idle_wiggle_stage is updated in wiggle_servos()
897 : }
898 : }
899 :
900 10396 : return false;
901 : }
902 :
903 : // verify_nav_delay - check if we have waited long enough
904 0 : bool ModeAuto::verify_nav_delay(const AP_Mission::Mission_Command& cmd)
905 : {
906 0 : if (AP::arming().is_armed_and_safety_off()) {
907 : // don't delay while armed, we need a nav controller running
908 0 : return true;
909 : }
910 0 : if (millis() - nav_delay.time_start_ms > nav_delay.time_max_ms) {
911 0 : nav_delay.time_max_ms = 0;
912 0 : return true;
913 : }
914 0 : return false;
915 : }
916 :
917 : /********************************************************************************/
918 : // Condition (May) commands
919 : /********************************************************************************/
920 :
921 0 : void Plane::do_wait_delay(const AP_Mission::Mission_Command& cmd)
922 : {
923 0 : condition_start = millis();
924 0 : condition_value = cmd.content.delay.seconds * 1000; // convert seconds to milliseconds
925 0 : }
926 :
927 0 : void Plane::do_within_distance(const AP_Mission::Mission_Command& cmd)
928 : {
929 0 : condition_value = cmd.content.distance.meters;
930 0 : }
931 :
932 : /********************************************************************************/
933 : // Verify Condition (May) commands
934 : /********************************************************************************/
935 :
936 0 : bool Plane::verify_wait_delay()
937 : {
938 0 : if ((unsigned)(millis() - condition_start) > (unsigned)condition_value) {
939 0 : condition_value = 0;
940 0 : return true;
941 : }
942 0 : return false;
943 : }
944 :
945 0 : bool Plane::verify_within_distance()
946 : {
947 0 : if (auto_state.wp_distance < MAX(condition_value,0)) {
948 0 : condition_value = 0;
949 0 : return true;
950 : }
951 0 : return false;
952 : }
953 :
954 : /********************************************************************************/
955 : // Do (Now) commands
956 : /********************************************************************************/
957 :
958 40 : void Plane::do_loiter_at_location()
959 : {
960 40 : if (aparm.loiter_radius < 0) {
961 0 : loiter.direction = -1;
962 : } else {
963 40 : loiter.direction = 1;
964 : }
965 40 : next_WP_loc = current_loc;
966 40 : }
967 :
968 11 : bool Plane::do_change_speed(const AP_Mission::Mission_Command& cmd)
969 : {
970 11 : return do_change_speed(
971 11 : (SPEED_TYPE)cmd.content.speed.speed_type,
972 11 : cmd.content.speed.target_ms,
973 11 : cmd.content.speed.throttle_pct
974 11 : );
975 : }
976 :
977 39 : bool Plane::do_change_speed(SPEED_TYPE speedtype, float speed_target_ms, float throttle_pct)
978 : {
979 39 : switch (speedtype) {
980 25 : case SPEED_TYPE_AIRSPEED:
981 25 : if (is_equal(speed_target_ms, -2.0f)) {
982 2 : new_airspeed_cm = -1; // return to default airspeed
983 2 : return true;
984 46 : } else if ((speed_target_ms >= aparm.airspeed_min.get()) &&
985 23 : (speed_target_ms <= aparm.airspeed_max.get())) {
986 23 : new_airspeed_cm = speed_target_ms * 100; //new airspeed target for AUTO or GUIDED modes
987 23 : gcs().send_text(MAV_SEVERITY_INFO, "Set airspeed %u m/s", (unsigned)speed_target_ms);
988 23 : return true;
989 : }
990 0 : break;
991 8 : case SPEED_TYPE_GROUNDSPEED:
992 8 : gcs().send_text(MAV_SEVERITY_INFO, "Set groundspeed %u", (unsigned)speed_target_ms);
993 8 : aparm.min_groundspeed.set(speed_target_ms);
994 8 : return true;
995 :
996 6 : case SPEED_TYPE_CLIMB_SPEED:
997 : case SPEED_TYPE_DESCENT_SPEED:
998 : case SPEED_TYPE_ENUM_END:
999 6 : break;
1000 : }
1001 :
1002 6 : if (throttle_pct > 0 && throttle_pct <= 100) {
1003 6 : gcs().send_text(MAV_SEVERITY_INFO, "Set throttle %u", (unsigned)throttle_pct);
1004 6 : aparm.throttle_cruise.set(throttle_pct);
1005 6 : return true;
1006 : }
1007 :
1008 0 : return false;
1009 : }
1010 :
1011 0 : void Plane::do_set_home(const AP_Mission::Mission_Command& cmd)
1012 : {
1013 0 : if (cmd.p1 == 1 && gps.status() >= AP_GPS_FixType::FIX_3D) {
1014 0 : if (!set_home_persistently(gps.location())) {
1015 : // silently ignore error
1016 : }
1017 : } else {
1018 0 : if (!AP::ahrs().set_home(cmd.content.location)) {
1019 : // silently ignore failure
1020 : }
1021 : }
1022 0 : }
1023 :
1024 : // start_command_callback - callback function called from ap-mission when it begins a new mission command
1025 : // we double check that the flight mode is AUTO to avoid the possibility of ap-mission triggering actions while we're not in AUTO mode
1026 1359 : bool Plane::start_command_callback(const AP_Mission::Mission_Command &cmd)
1027 : {
1028 1359 : if (control_mode == &mode_auto) {
1029 1329 : return start_command(cmd);
1030 : }
1031 30 : return true;
1032 : }
1033 :
1034 : // verify_command_callback - callback function called from ap-mission at 10hz or higher when a command is being run
1035 : // we double check that the flight mode is AUTO to avoid the possibility of ap-mission triggering actions while we're not in AUTO mode
1036 245563 : bool Plane::verify_command_callback(const AP_Mission::Mission_Command& cmd)
1037 : {
1038 245563 : if (control_mode == &mode_auto) {
1039 245563 : bool cmd_complete = verify_command(cmd);
1040 :
1041 : // send message to GCS
1042 245563 : if (cmd_complete) {
1043 1031 : gcs().send_mission_item_reached_message(cmd.index);
1044 : }
1045 :
1046 245563 : return cmd_complete;
1047 : }
1048 0 : return false;
1049 : }
1050 :
1051 : // exit_mission_callback - callback function called from ap-mission when the mission has completed
1052 : // we double check that the flight mode is AUTO to avoid the possibility of ap-mission triggering actions while we're not in AUTO mode
1053 5 : void Plane::exit_mission_callback()
1054 : {
1055 5 : if (control_mode == &mode_auto) {
1056 5 : set_mode(mode_rtl, ModeReason::MISSION_END);
1057 5 : gcs().send_text(MAV_SEVERITY_INFO, "Mission complete, changing mode to RTL");
1058 : }
1059 5 : }
1060 :
1061 : #if HAL_QUADPLANE_ENABLED
1062 2590 : bool Plane::verify_landing_vtol_approach(const AP_Mission::Mission_Command &cmd)
1063 : {
1064 2590 : const float radius = is_zero(quadplane.fw_land_approach_radius_m)? aparm.loiter_radius : quadplane.fw_land_approach_radius_m;
1065 2590 : const int8_t direction = is_negative(radius) ? -1 : 1;
1066 2590 : const float abs_radius = fabsf(radius);
1067 :
1068 2590 : loiter.direction = direction;
1069 :
1070 2590 : switch (vtol_approach_s.approach_stage) {
1071 0 : case VTOLApproach::Stage::RTL:
1072 : {
1073 : // fly home and loiter at RTL alt
1074 0 : nav_controller->update_loiter(cmd.content.location, abs_radius, direction);
1075 0 : if (plane.reached_loiter_target()) {
1076 : // descend to Q RTL alt
1077 0 : plane.do_RTL(plane.home.alt + plane.quadplane.qrtl_alt_m*100UL);
1078 0 : plane.loiter_angle_reset();
1079 0 : vtol_approach_s.approach_stage = VTOLApproach::Stage::LOITER_TO_ALT;
1080 : }
1081 0 : break;
1082 : }
1083 799 : case VTOLApproach::Stage::LOITER_TO_ALT:
1084 : {
1085 799 : nav_controller->update_loiter(cmd.content.location, abs_radius, direction);
1086 :
1087 799 : if (labs(loiter.sum_cd) > 1 && (loiter.reached_target_alt || loiter.unable_to_achieve_target_alt)) {
1088 2 : Vector3f wind;
1089 : // use the estimate even if it is not marked valid,
1090 : // to preserve existing behaviour
1091 2 : IGNORE_RETURN(ahrs.get_wind(wind));
1092 2 : vtol_approach_s.approach_direction_deg = degrees(atan2f(-wind.y, -wind.x));
1093 2 : gcs().send_text(MAV_SEVERITY_INFO, "Selected an approach path of %.1f", (double)vtol_approach_s.approach_direction_deg);
1094 2 : vtol_approach_s.approach_stage = VTOLApproach::Stage::ENSURE_RADIUS;
1095 : }
1096 799 : break;
1097 : }
1098 2 : case VTOLApproach::Stage::ENSURE_RADIUS:
1099 : {
1100 : // validate that the vehicle is at least the expected distance away from the loiter point
1101 : // require an angle total of at least 2 centidegrees, due to special casing of 1 centidegree
1102 2 : if (((fabsF(cmd.content.location.get_distance(current_loc) - abs_radius) > 5.0f) &&
1103 4 : (cmd.content.location.get_distance(current_loc) < abs_radius)) ||
1104 2 : (labs(loiter.sum_cd) < 2)) {
1105 0 : nav_controller->update_loiter(cmd.content.location, abs_radius, direction);
1106 0 : break;
1107 : }
1108 2 : vtol_approach_s.approach_stage = VTOLApproach::Stage::WAIT_FOR_BREAKOUT;
1109 : FALLTHROUGH;
1110 : }
1111 173 : case VTOLApproach::Stage::WAIT_FOR_BREAKOUT:
1112 : {
1113 173 : nav_controller->update_loiter(cmd.content.location, radius, direction);
1114 :
1115 173 : const float breakout_direction_rad = radians(vtol_approach_s.approach_direction_deg + (direction > 0 ? 270 : 90));
1116 :
1117 : // breakout when within 5 degrees of the opposite direction
1118 173 : if (fabsF(wrap_PI(ahrs.get_yaw_rad() - breakout_direction_rad)) < radians(5.0f)) {
1119 2 : gcs().send_text(MAV_SEVERITY_INFO, "Starting VTOL land approach path");
1120 2 : vtol_approach_s.approach_stage = VTOLApproach::Stage::APPROACH_LINE;
1121 2 : set_next_WP(cmd.content.location);
1122 : // fallthrough
1123 : } else {
1124 171 : break;
1125 : }
1126 : FALLTHROUGH;
1127 : }
1128 : case VTOLApproach::Stage::APPROACH_LINE:
1129 : {
1130 : // project an approach path
1131 58 : Location start = cmd.content.location;
1132 58 : Location end = cmd.content.location;
1133 :
1134 : // project a 1km waypoint to either side of the landing location
1135 58 : start.offset_bearing(vtol_approach_s.approach_direction_deg + 180, 1000);
1136 58 : end.offset_bearing(vtol_approach_s.approach_direction_deg, 1000);
1137 :
1138 58 : nav_controller->update_waypoint(start, end);
1139 :
1140 : // check if we should move on to the next waypoint
1141 58 : Location breakout_stopping_loc = cmd.content.location;
1142 58 : breakout_stopping_loc.offset_bearing(vtol_approach_s.approach_direction_deg + 180, quadplane.stopping_distance_m());
1143 58 : const bool past_finish_line = current_loc.past_interval_finish_line(start, breakout_stopping_loc);
1144 :
1145 58 : Location breakout_loc = cmd.content.location;
1146 58 : breakout_loc.offset_bearing(vtol_approach_s.approach_direction_deg + 180, abs_radius);
1147 58 : const bool half_radius = current_loc.line_path_proportion(breakout_loc, cmd.content.location) > 0.5;
1148 58 : bool lined_up = true;
1149 58 : Vector3f vel_NED;
1150 58 : if (ahrs.get_velocity_NED(vel_NED)) {
1151 58 : const Vector2f target_vec = current_loc.get_distance_NE(cmd.content.location);
1152 58 : const float angle_err = fabsf(wrap_180(degrees(vel_NED.xy().angle(target_vec))));
1153 58 : lined_up = (angle_err < 30);
1154 : }
1155 :
1156 58 : if (past_finish_line && (lined_up || half_radius)) {
1157 2 : vtol_approach_s.approach_stage = VTOLApproach::Stage::VTOL_LANDING;
1158 2 : quadplane.do_vtol_land(cmd);
1159 : // fallthrough
1160 : } else {
1161 : break;
1162 : }
1163 : FALLTHROUGH;
1164 : }
1165 : case VTOLApproach::Stage::VTOL_LANDING:
1166 : // nothing to do here, we should be into the quadplane landing code
1167 1564 : return true;
1168 : }
1169 :
1170 1026 : return false;
1171 : }
1172 : #endif // HAL_QUADPLANE_ENABLED
1173 :
1174 2879 : bool Plane::verify_loiter_heading(bool init)
1175 : {
1176 : #if HAL_QUADPLANE_ENABLED
1177 2879 : if (quadplane.in_vtol_auto()) {
1178 : // skip heading verify if in VTOL auto
1179 0 : return true;
1180 : }
1181 : #endif
1182 :
1183 : #if MODE_AUTOLAND_ENABLED
1184 2879 : if (control_mode == &mode_autoland) {
1185 : // autoland mode has its own lineup criterion
1186 434 : return mode_autoland.landing_lined_up();
1187 : }
1188 : #endif
1189 :
1190 : //Get the lat/lon of next Nav waypoint after this one:
1191 2445 : AP_Mission::Mission_Command next_nav_cmd;
1192 2445 : if (! mission.get_next_nav_cmd(mission.get_current_nav_index() + 1,
1193 : next_nav_cmd)) {
1194 : //no next waypoint to shoot for -- go ahead and break out of loiter
1195 0 : return true;
1196 : }
1197 :
1198 2445 : if (init) {
1199 15 : loiter.sum_cd = 0;
1200 : }
1201 :
1202 2445 : return plane.mode_loiter.isHeadingLinedUp(next_WP_loc, next_nav_cmd.content.location);
1203 : }
1204 :
1205 145036 : float Plane::get_wp_radius() const
1206 : {
1207 : #if HAL_QUADPLANE_ENABLED
1208 145036 : if (plane.quadplane.in_vtol_mode()) {
1209 885 : return plane.quadplane.wp_nav->get_wp_radius_m();
1210 : }
1211 : #endif
1212 144151 : return g.waypoint_radius;
1213 : }
1214 :
1215 : #if AP_SCRIPTING_ENABLED
1216 : /*
1217 : support for scripted navigation, with verify operation for completion
1218 : */
1219 0 : void Plane::do_nav_script_time(const AP_Mission::Mission_Command& cmd)
1220 : {
1221 0 : nav_scripting.enabled = true;
1222 0 : nav_scripting.id++;
1223 0 : nav_scripting.start_ms = AP_HAL::millis();
1224 0 : nav_scripting.current_ms = nav_scripting.start_ms;
1225 :
1226 : // start with current roll rate, pitch rate and throttle
1227 0 : nav_scripting.roll_rate_dps = plane.rollController.get_pid_info().target;
1228 0 : nav_scripting.pitch_rate_dps = plane.pitchController.get_pid_info().target;
1229 0 : nav_scripting.yaw_rate_dps = degrees(ahrs.get_gyro().z);
1230 0 : nav_scripting.throttle_pct = SRV_Channels::get_output_scaled(SRV_Channel::k_throttle);
1231 0 : }
1232 :
1233 : /*
1234 : wait for scripting to say that the mission item is complete
1235 : */
1236 0 : bool Plane::verify_nav_script_time(const AP_Mission::Mission_Command& cmd)
1237 : {
1238 0 : if (cmd.content.nav_script_time.timeout_s > 0) {
1239 0 : const uint32_t now = AP_HAL::millis();
1240 0 : if (now - nav_scripting.start_ms > cmd.content.nav_script_time.timeout_s*1000U) {
1241 0 : gcs().send_text(MAV_SEVERITY_INFO, "NavScriptTime timed out");
1242 0 : nav_scripting.enabled = false;
1243 0 : nav_scripting.rudder_offset_pct = 0;
1244 0 : nav_scripting.run_yaw_rate_controller = true;
1245 : }
1246 : }
1247 0 : return !nav_scripting.enabled;
1248 : }
1249 :
1250 : // check if we are in a NAV_SCRIPT_* command
1251 61007492 : bool Plane::nav_scripting_active(void)
1252 : {
1253 61007492 : if (nav_scripting.enabled && AP_HAL::millis() - nav_scripting.current_ms > 1000) {
1254 : // set_target_throttle_rate_rpy has not been called from script in last 1000ms
1255 0 : nav_scripting.enabled = false;
1256 0 : nav_scripting.current_ms = 0;
1257 0 : nav_scripting.rudder_offset_pct = 0;
1258 0 : nav_scripting.run_yaw_rate_controller = true;
1259 0 : gcs().send_text(MAV_SEVERITY_INFO, "NavScript time out");
1260 : }
1261 108837379 : if (control_mode == &mode_auto &&
1262 47829887 : mission.get_current_nav_cmd().id != MAV_CMD_NAV_SCRIPT_TIME) {
1263 47829887 : nav_scripting.enabled = false;
1264 : }
1265 61007492 : return nav_scripting.enabled;
1266 : }
1267 :
1268 : // support for NAV_SCRIPTING mission command
1269 0 : bool Plane::nav_script_time(uint16_t &id, uint8_t &cmd, float &arg1, float &arg2, int16_t &arg3, int16_t &arg4)
1270 : {
1271 0 : if (!nav_scripting_active()) {
1272 : // not in NAV_SCRIPT_TIME
1273 0 : return false;
1274 : }
1275 0 : const auto &c = mission.get_current_nav_cmd().content.nav_script_time;
1276 0 : id = nav_scripting.id;
1277 0 : cmd = c.command;
1278 0 : arg1 = c.arg1.get();
1279 0 : arg2 = c.arg2.get();
1280 0 : arg3 = c.arg3;
1281 0 : arg4 = c.arg4;
1282 0 : return true;
1283 : }
1284 :
1285 : // called when script has completed the command
1286 0 : void Plane::nav_script_time_done(uint16_t id)
1287 : {
1288 0 : if (id == nav_scripting.id) {
1289 0 : nav_scripting.enabled = false;
1290 : }
1291 0 : }
1292 :
1293 : // support for NAV_SCRIPTING mission command and aerobatics in other allowed modes
1294 4953 : void Plane::set_target_throttle_rate_rpy(float throttle_pct, float roll_rate_dps, float pitch_rate_dps, float yaw_rate_dps)
1295 : {
1296 4953 : nav_scripting.roll_rate_dps = constrain_float(roll_rate_dps, -g.acro_roll_rate, g.acro_roll_rate);
1297 4953 : nav_scripting.pitch_rate_dps = constrain_float(pitch_rate_dps, -g.acro_pitch_rate, g.acro_pitch_rate);
1298 4953 : nav_scripting.yaw_rate_dps = constrain_float(yaw_rate_dps, -g.acro_yaw_rate, g.acro_yaw_rate);
1299 4953 : nav_scripting.throttle_pct = constrain_float(throttle_pct, aparm.throttle_min, aparm.throttle_max);
1300 4953 : nav_scripting.current_ms = AP_HAL::millis();
1301 4953 : }
1302 :
1303 : // support for rudder offset override in aerobatic scripting
1304 4953 : void Plane::set_rudder_offset(float rudder_pct, bool run_yaw_rate_controller)
1305 : {
1306 4953 : nav_scripting.rudder_offset_pct = rudder_pct;
1307 4953 : nav_scripting.run_yaw_rate_controller = run_yaw_rate_controller;
1308 4953 : }
1309 :
1310 : // enable NAV_SCRIPTING takeover in modes other than AUTO using script time mission commands
1311 2 : bool Plane::nav_scripting_enable(uint8_t mode)
1312 : {
1313 2 : uint8_t current_control_mode = control_mode->mode_number();
1314 2 : if (current_control_mode == mode) {
1315 1 : switch (current_control_mode) {
1316 1 : case Mode::Number::CIRCLE:
1317 : case Mode::Number::STABILIZE:
1318 : case Mode::Number::ACRO:
1319 : case Mode::Number::FLY_BY_WIRE_A:
1320 : case Mode::Number::FLY_BY_WIRE_B:
1321 : case Mode::Number::CRUISE:
1322 : case Mode::Number::LOITER:
1323 1 : nav_scripting.enabled = true;
1324 1 : nav_scripting.current_ms = AP_HAL::millis();
1325 1 : break;
1326 0 : default:
1327 0 : nav_scripting.enabled = false;
1328 : }
1329 : } else {
1330 1 : nav_scripting.enabled = false;
1331 : }
1332 2 : return nav_scripting.enabled;
1333 : }
1334 : #endif // AP_SCRIPTING_ENABLED
1335 :
1336 : /*
1337 : return true if this is a LAND command
1338 : note that we consider a PAYLOAD_PLACE to be a land command as it
1339 : follows the landing logic for quadplanes
1340 : */
1341 111403 : bool Plane::is_land_command(uint16_t command) const
1342 : {
1343 : return
1344 107999 : command == MAV_CMD_NAV_VTOL_LAND ||
1345 219402 : command == MAV_CMD_NAV_LAND ||
1346 111403 : command == MAV_CMD_NAV_PAYLOAD_PLACE;
1347 : }
1348 :
1349 : /*
1350 : return true if in a specific AUTO mission command
1351 : */
1352 235485 : bool Plane::in_auto_mission_id(uint16_t command) const
1353 : {
1354 235485 : return control_mode == &mode_auto && mission.get_current_nav_id() == command;
1355 : }
1356 :
|