#include "Copter.h" #if MODE_FOLLOW_ENABLED == ENABLED /* * mode_follow.cpp - follow another mavlink-enabled vehicle by system id * * TODO: stick control to move around on sphere * TODO: stick control to change sphere diameter * TODO: "channel 7 option" to lock onto "pointed at" target * TODO: do better in terms of loitering around the moving point; may need a PID? Maybe use loiter controller somehow? * TODO: extrapolate target vehicle position using its velocity and acceleration * TODO: ensure AP_AVOIDANCE_ENABLED is true because we rely on it velocity limiting functions */ // initialise follow mode bool ModeFollow::init(const bool ignore_checks) { if (!g2.follow.enabled()) { gcs().send_text(MAV_SEVERITY_WARNING, "Set FOLL_ENABLE = 1"); return false; } #if HAL_MOUNT_ENABLED AP_Mount *mount = AP_Mount::get_singleton(); // follow the lead vehicle using sysid if (g2.follow.option_is_enabled(AP_Follow::Option::MOUNT_FOLLOW_ON_ENTER) && mount != nullptr) { mount->set_target_sysid(g2.follow.get_target_sysid()); } #endif // re-use guided mode return ModeGuided::init(ignore_checks); } // perform cleanup required when leaving follow mode void ModeFollow::exit() { g2.follow.clear_offsets_if_required(); } void ModeFollow::run() { // if not armed set throttle to zero and exit immediately if (is_disarmed_or_landed()) { make_safe_ground_handling(); return; } // set motors to full range motors->set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED); // re-use guided mode's velocity controller // Note: this is safe from interference from GCSs and companion computer's whose guided mode // position and velocity requests will be ignored while the vehicle is not in guided mode // variables to be sent to velocity controller Vector3f desired_velocity_neu_cms; bool use_yaw = false; float yaw_cd = 0.0f; Vector3f dist_vec; // vector to lead vehicle Vector3f dist_vec_offs; // vector to lead vehicle + offset Vector3f vel_of_target; // velocity of lead vehicle if (g2.follow.get_target_dist_and_vel_ned(dist_vec, dist_vec_offs, vel_of_target)) { // convert dist_vec_offs to cm in NEU const Vector3f dist_vec_offs_neu(dist_vec_offs.x * 100.0f, dist_vec_offs.y * 100.0f, -dist_vec_offs.z * 100.0f); // calculate desired relative velocity vector in cm/s in NEU const float kp = g2.follow.get_pos_p().kP(); desired_velocity_neu_cms = dist_vec_offs_neu * kp; // create horizontal desired velocity vector (required for slow down calculations) Vector2f desired_velocity_xy_cms(desired_velocity_neu_cms.x, desired_velocity_neu_cms.y); // create horizontal unit vector towards target (required for slow down calculations) Vector2f dir_to_target_xy(desired_velocity_xy_cms.x, desired_velocity_xy_cms.y); if (!dir_to_target_xy.is_zero()) { dir_to_target_xy.normalize(); } // slow down horizontally as we approach target (use 1/2 of maximum deceleration for gentle slow down) const float dist_to_target_xy = Vector2f(dist_vec_offs_neu.x, dist_vec_offs_neu.y).length(); copter.avoid.limit_velocity_2D(pos_control->get_pos_xy_p().kP().get(), pos_control->get_max_accel_xy_cmss() * 0.5f, desired_velocity_xy_cms, dir_to_target_xy, dist_to_target_xy, copter.G_Dt); // copy horizontal velocity limits back to 3d vector desired_velocity_neu_cms.xy() = desired_velocity_xy_cms; // limit vertical desired_velocity_neu_cms to slow as we approach target (we use 1/2 of maximum deceleration for gentle slow down) const float des_vel_z_max = copter.avoid.get_max_speed(pos_control->get_pos_z_p().kP().get(), pos_control->get_max_accel_z_cmss() * 0.5f, fabsf(dist_vec_offs_neu.z), copter.G_Dt); desired_velocity_neu_cms.z = constrain_float(desired_velocity_neu_cms.z, -des_vel_z_max, des_vel_z_max); // Add the target velocity baseline. desired_velocity_neu_cms.xy() += vel_of_target.xy() * 100.0f; desired_velocity_neu_cms.z += -vel_of_target.z * 100.0f; // scale desired velocity to stay within horizontal speed limit desired_velocity_neu_cms.xy().limit_length(pos_control->get_max_speed_xy_cms()); // limit desired velocity to be between maximum climb and descent rates desired_velocity_neu_cms.z = constrain_float(desired_velocity_neu_cms.z, -fabsf(pos_control->get_max_speed_down_cms()), pos_control->get_max_speed_up_cms()); // limit the velocity for obstacle/fence avoidance copter.avoid.adjust_velocity(desired_velocity_neu_cms, pos_control->get_pos_xy_p().kP().get(), pos_control->get_max_accel_xy_cmss(), pos_control->get_pos_z_p().kP().get(), pos_control->get_max_accel_z_cmss(), G_Dt); // calculate vehicle heading switch (g2.follow.get_yaw_behave()) { case AP_Follow::YAW_BEHAVE_FACE_LEAD_VEHICLE: { if (dist_vec.xy().length_squared() > 1.0) { yaw_cd = get_bearing_cd(Vector2f{}, dist_vec.xy()); use_yaw = true; } break; } case AP_Follow::YAW_BEHAVE_SAME_AS_LEAD_VEHICLE: { float target_hdg = 0.0f; if (g2.follow.get_target_heading_deg(target_hdg)) { yaw_cd = target_hdg * 100.0f; use_yaw = true; } break; } case AP_Follow::YAW_BEHAVE_DIR_OF_FLIGHT: { if (desired_velocity_neu_cms.xy().length_squared() > (100.0 * 100.0)) { yaw_cd = get_bearing_cd(Vector2f{}, desired_velocity_neu_cms.xy()); use_yaw = true; } break; } case AP_Follow::YAW_BEHAVE_NONE: default: // do nothing break; } } // log output at 10hz uint32_t now = AP_HAL::millis(); bool log_request = false; if ((now - last_log_ms >= 100) || (last_log_ms == 0)) { log_request = true; last_log_ms = now; } // re-use guided mode's velocity controller (takes NEU) ModeGuided::set_velocity(desired_velocity_neu_cms, use_yaw, yaw_cd, false, 0.0f, false, log_request); ModeGuided::run(); } uint32_t ModeFollow::wp_distance() const { return g2.follow.get_distance_to_target() * 100; } int32_t ModeFollow::wp_bearing() const { return g2.follow.get_bearing_to_target() * 100; } /* get target position for mavlink reporting */ bool ModeFollow::get_wp(Location &loc) const { float dist = g2.follow.get_distance_to_target(); float bearing = g2.follow.get_bearing_to_target(); loc = copter.current_loc; loc.offset_bearing(bearing, dist); return true; } #endif // MODE_FOLLOW_ENABLED == ENABLED