-- This script allows for best effort return for quadplanes
-- on loss of forward motor

-- When an engine failure is detected (RPM and Vibration values drop below a certain threshold), the aircraft will:
-- Send a warning ground station
-- Start prioritizing airspeed completely
-- Glide back towards the home location or rally point, monitoring the distance away from the point as well as Altitude.
-- If the aircraft drops below a predetermined minimum altitude, QLAND mode is engaged and the aircraft lands at its current position.
-- If the aircraft arrives within Q_FW_LND_APR_RAD of the return point before dropping below the minimum altitude, it should loiter down to the minimum altitude before switching to QRTL and landing.

-- consider engine stopped when vibe is low and RPM low for more than 4s
local ENGINE_STOPPED_MS = 4000

-- RPM threshold below which engine may be stopped
local RPM_LOW_THRESH = 500

-- vibration threshold below which engine may be stopped
local VIBE_LOW_THRESH = 2

-- altitude threshold for QLAND
local LOW_ALT_THRESH = 70

-- when below MIN_AIRSPEED assume we are not in forward flight
local MIN_AIRSPEED = 5

-- time when engine stopped
local engine_stop_ms = -1
local engine_stopped = false

-- have we triggered failsafe? Only trigger once
local triggered_failsafe = false

-- flight mode numbers for plane
local MODE_AUTO = 10
local MODE_RTL = 11
local MODE_QLAND = 20
local MODE_QRTL = 21

-- update engine running status
function check_engine()
  if not arming:is_armed() then
     engine_stopped = false
     engine_stop_ms = -1
     return true
  end

  local rpm = RPM:get_rpm(0)
  local vibe = ahrs:get_vibration():length()

  -- if either RPM is high or vibe is high then assume engine is running
  if (rpm and (rpm > RPM_LOW_THRESH)) or (vibe > VIBE_LOW_THRESH) then
     -- engine is definately running
     engine_stop_ms = -1
     if engine_stopped then
        -- notify user engine has started
        gcs:send_text(0, "Engine: STARTED")
        engine_stopped = false
     end
     return true
  end
  local now = millis()

  if engine_stop_ms == -1 then
     -- start timeout period
     engine_stop_ms = now
     return true
  end
  if now - engine_stop_ms < ENGINE_STOPPED_MS then
     return false
  end
  -- engine has been stopped for ENGINE_STOPPED_MS milliseconds, notify user
  if not engine_stopped then
     engine_stopped = true
     gcs:send_text(0, "Engine: STOPPED")
  end
  return engine_stopped
end

-- trigger failsafe function
function trigger_failsafe()
  if param:get('RTL_AUTOLAND') == 2 then
     -- we don't want to do the mission based autoland, so disable
     -- RTL_AUTOLAND
     param:set('RTL_AUTOLAND', 0)
  end
  if param:get('TECS_SPDWEIGHT') < 2 then
     -- force airspeed priority
     param:set('TECS_SPDWEIGHT', 2)
  end
  -- trigger an RTL to start bringing the vehicle home
  -- it will automatically go to the nearest rally point if set or go to home
  -- if no rally point available within the RALLY_LIMIT_KM
  vehicle:set_mode(MODE_RTL)
end

-- check if we should switch to QLAND
function check_qland()
  local target = vehicle:get_target_location()
  local pos = ahrs:get_position()
  if not target or not pos then
    -- we can't estimate distance
    return
  end
  local dist = target:get_distance(pos)
  local terrain_height = terrain:height_above_terrain(true)
  local threshold = param:get('Q_FW_LND_APR_RAD')
  if dist < threshold and (terrain_height and (terrain_height < LOW_ALT_THRESH)) then
    gcs:send_text(0, "Failsafe: LANDING QRTL")
    vehicle:set_mode(MODE_QRTL)
  elseif terrain_height and terrain_height < LOW_ALT_THRESH then
    gcs:send_text(0, "Failsafe: LANDING QLAND")
    vehicle:set_mode(MODE_QLAND)
  end
end

function update()
  -- check engine status
  check_engine()

  -- if armed and in AUTO mode then consider triggering failsafe
  if engine_stopped and vehicle:get_mode() == MODE_AUTO and arming:is_armed() and not triggered_failsafe then
    triggered_failsafe = true
    trigger_failsafe()
    gcs:send_text(0, "Failsafe: TRIGGERED")
  end

  if not engine_stopped and triggered_failsafe then
    triggered_failsafe = false
    gcs:send_text(0, "Failsafe: RECOVERED")
  end

  if triggered_failsafe and vehicle:get_mode() == MODE_RTL then
    check_qland()
  end

  -- run at 5Hz
  return update, 200
end

-- start running update loop
return update()
