#include "Copter.h"
#define CRASH_CHECK_TRIGGER_SEC 2
#define CRASH_CHECK_ANGLE_DEVIATION_DEG 30.0f
#define CRASH_CHECK_ANGLE_MIN_DEG 15.0f
#define CRASH_CHECK_SPEED_MAX 10.0f
#define CRASH_CHECK_ACCEL_MAX 3.0f
#define THRUST_LOSS_CHECK_TRIGGER_SEC 1
#define THRUST_LOSS_CHECK_ANGLE_DEVIATION_RAD radians(15.0)
#define THRUST_LOSS_CHECK_MINIMUM_THROTTLE 0.9f
#define YAW_IMBALANCE_IMAX_THRESHOLD 0.75f
#define YAW_IMBALANCE_WARN_MS 10000
void Copter::crash_check()
{
static uint16_t crash_counter;
if (!motors->armed() || ap.land_complete || g.fs_crash_check == 0) {
crash_counter = 0;
return;
}
if (standby_active) {
crash_counter = 0;
return;
}
if (get_force_flying() && !flightmode->is_landing()) {
crash_counter = 0;
return;
}
if (!flightmode->crash_check_enabled()) {
crash_counter = 0;
return;
}
#if MODE_AUTOROTATE_ENABLED
if (flightmode->mode_number() == Mode::Number::AUTOROTATE) {
crash_counter = 0;
return;
}
#endif
const float filtered_acc = land_accel_ef_filter.get().length();
if (filtered_acc >= CRASH_CHECK_ACCEL_MAX) {
crash_counter = 0;
return;
}
const float lean_angle_deg = degrees(acosf(ahrs.cos_roll()*ahrs.cos_pitch()));
if (lean_angle_deg <= CRASH_CHECK_ANGLE_MIN_DEG) {
crash_counter = 0;
return;
}
const float angle_error = attitude_control->get_att_error_angle_deg();
if (angle_error <= CRASH_CHECK_ANGLE_DEVIATION_DEG) {
crash_counter = 0;
return;
}
Vector3f vel_ned;
if (ahrs.get_velocity_NED(vel_ned) && (vel_ned.length() >= CRASH_CHECK_SPEED_MAX)) {
crash_counter = 0;
return;
}
crash_counter++;
if (crash_counter >= (CRASH_CHECK_TRIGGER_SEC * scheduler.get_loop_rate_hz())) {
LOGGER_WRITE_ERROR(LogErrorSubsystem::CRASH_CHECK, LogErrorCode::CRASH_CHECK_CRASH);
gcs().send_text(MAV_SEVERITY_EMERGENCY,"Crash: Disarming: AngErr=%.0f>%.0f, Accel=%.1f<%.1f", angle_error, CRASH_CHECK_ANGLE_DEVIATION_DEG, filtered_acc, CRASH_CHECK_ACCEL_MAX);
copter.arming.disarm(AP_Arming::Method::CRASH);
}
}
void Copter::thrust_loss_check()
{
static uint16_t thrust_loss_counter;
if (copter.option_is_enabled(FlightOption::DISABLE_THRUST_LOSS_CHECK)) {
return;
}
if (motors->get_thrust_boost()) {
return;
}
if (!motors->armed() || ap.land_complete) {
thrust_loss_counter = 0;
return;
}
if (standby_active) {
return;
}
const Vector3f& angle_target_rad = attitude_control->get_att_target_euler_rad();
if (angle_target_rad.xy().length_squared() > sq(THRUST_LOSS_CHECK_ANGLE_DEVIATION_RAD)) {
thrust_loss_counter = 0;
return;
}
if ((attitude_control->get_throttle_in() < THRUST_LOSS_CHECK_MINIMUM_THROTTLE) && (!motors->limit.throttle_upper)) {
thrust_loss_counter = 0;
return;
}
if ((attitude_control->get_throttle_in() < 0.25f)) {
thrust_loss_counter = 0;
return;
}
float vel_d_ms = 0;
if (!AP::ahrs().get_velocity_D(vel_d_ms, vibration_check.high_vibes) || !is_positive(vel_d_ms)) {
thrust_loss_counter = 0;
return;
}
const float angle_error = attitude_control->get_att_error_angle_deg();
if (angle_error >= CRASH_CHECK_ANGLE_DEVIATION_DEG) {
thrust_loss_counter = 0;
return;
}
thrust_loss_counter++;
if (thrust_loss_counter >= (THRUST_LOSS_CHECK_TRIGGER_SEC * scheduler.get_loop_rate_hz())) {
thrust_loss_counter = 0;
LOGGER_WRITE_ERROR(LogErrorSubsystem::THRUST_LOSS_CHECK, LogErrorCode::FAILSAFE_OCCURRED);
gcs().send_text(MAV_SEVERITY_EMERGENCY, "Potential Thrust Loss (%d)", (int)motors->get_lost_motor() + 1);
motors->set_thrust_boost(true);
#if AP_GRIPPER_ENABLED
if (copter.option_is_enabled(FlightOption::RELEASE_GRIPPER_ON_THRUST_LOSS)) {
gripper.release();
}
#endif
}
}
void Copter::yaw_imbalance_check()
{
if (copter.option_is_enabled(FlightOption::DISABLE_YAW_IMBALANCE_WARNING)) {
return;
}
if (!is_positive(attitude_control->get_rate_yaw_pid().kI())) {
return;
}
if (motors->get_thrust_boost()) {
yaw_I_filt.reset(0.0f);
return;
}
if (!motors->armed() || ap.land_complete) {
yaw_I_filt.reset(0.0f);
return;
}
if (standby_active) {
yaw_I_filt.reset(0.0f);
return;
}
const float I_term = attitude_control->get_rate_yaw_pid().get_pid_info().I;
const float I = fabsf(yaw_I_filt.apply(attitude_control->get_rate_yaw_pid().get_pid_info().I,G_Dt));
if (I > fabsf(I_term)) {
yaw_I_filt.reset(I_term);
}
const float I_max = attitude_control->get_rate_yaw_pid().imax();
if ((is_positive(I_max) && ((I > YAW_IMBALANCE_IMAX_THRESHOLD * I_max) || (is_equal(I_term,I_max))))) {
const uint32_t now = millis();
if (now - last_yaw_warn_ms > YAW_IMBALANCE_WARN_MS) {
last_yaw_warn_ms = now;
gcs().send_text(MAV_SEVERITY_EMERGENCY, "Yaw Imbalance %0.0f%%", I *100);
}
}
}
#if HAL_PARACHUTE_ENABLED
#define PARACHUTE_CHECK_TRIGGER_SEC 1
#define PARACHUTE_CHECK_ANGLE_DEVIATION_DEG 30.0f
void Copter::parachute_check()
{
static uint16_t control_loss_count;
static float baro_alt_start_m;
if (!parachute.enabled()) {
return;
}
parachute.set_is_flying(!ap.land_complete);
float vel_d_ms = 0;
UNUSED_RESULT(AP::ahrs().get_velocity_D(vel_d_ms, vibration_check.high_vibes));
parachute.set_sink_rate(vel_d_ms);
if (standby_active) {
return;
}
parachute.update();
if (!motors->armed()) {
control_loss_count = 0;
return;
}
if (parachute.release_initiated()) {
return;
}
if (!flightmode->crash_check_enabled()) {
control_loss_count = 0;
return;
}
if (ap.land_complete) {
control_loss_count = 0;
return;
}
if (control_loss_count == 0 && parachute.alt_min() != 0 && (current_loc.alt < (int32_t)parachute.alt_min() * 100)) {
return;
}
parachute.check_sink_rate();
const float angle_error = attitude_control->get_att_error_angle_deg();
if (angle_error <= PARACHUTE_CHECK_ANGLE_DEVIATION_DEG) {
if (control_loss_count > 0) {
control_loss_count--;
}
return;
}
if (control_loss_count < (PARACHUTE_CHECK_TRIGGER_SEC*scheduler.get_loop_rate_hz())) {
control_loss_count++;
}
if (control_loss_count == 1) {
baro_alt_start_m = baro_alt_m;
} else if (baro_alt_m >= baro_alt_start_m) {
control_loss_count = 0;
return;
} else if (control_loss_count >= (PARACHUTE_CHECK_TRIGGER_SEC*scheduler.get_loop_rate_hz())) {
control_loss_count = 0;
LOGGER_WRITE_ERROR(LogErrorSubsystem::CRASH_CHECK, LogErrorCode::CRASH_CHECK_LOSS_OF_CONTROL);
parachute_release();
}
}
void Copter::parachute_release()
{
parachute.release();
#if AP_LANDINGGEAR_ENABLED
landinggear.set_position(AP_LandingGear::LandingGear_Deploy);
#endif
}
void Copter::parachute_manual_release()
{
if (!parachute.enabled()) {
return;
}
if (ap.land_complete) {
gcs().send_text(MAV_SEVERITY_INFO,"Parachute: Landed");
LOGGER_WRITE_ERROR(LogErrorSubsystem::PARACHUTES, LogErrorCode::PARACHUTE_LANDED);
return;
}
if ((parachute.alt_min() != 0 && (current_loc.alt < (int32_t)parachute.alt_min() * 100))) {
gcs().send_text(MAV_SEVERITY_ALERT,"Parachute: Too low");
LOGGER_WRITE_ERROR(LogErrorSubsystem::PARACHUTES, LogErrorCode::PARACHUTE_TOO_LOW);
return;
}
parachute_release();
}
#endif