Disable RTL on low battery failsafe when close to home

Hi,
I frequently face the issue that ardupilot switches to RTL during manual landing, resulting in a climb to RTL_ALT. With a large ~25kg octocopter this kind of behavior is a bit nasty. I am looking for a solution to disable the low battery failsafe action BATT_FS_LOW_ACT whenever my copter is reasonably close to home. In my eyes it would be a great feature for future releases to define home as an area instead of a point, but as long as something like this is not implemented I have to look for different solutions. I started looking into lua scripting and consider this as the most promising approach. Since I have not used lua scripts before I could need some help. Below my first attempt of such a lua script (based on the sample script on Lua Scripts — Copter documentation, which is actually really close to my problem). Would something like this work and be reasonably safe or am I forgetting anything critical? I am aware that one can have several battery monitors (up to 9) and my script will only work on the first one. For a more generalized version this should maybe be dealt with.
Thanks a lot,
Stephan



local home_radius = 10                                             --define a radius around home to define a home zone for which RTL on low battery failsafe will be disabled
local BATT_FS_LOW_ACT = Parameter()                                --creates a parameter object
BATT_FS_LOW_ACT:init('BATT_FS_LOW_ACT')                            --get the physical location in parameters memory area so no future name search is done
local low_batt_init_action = BATT_FS_LOW_ACT:get()                 --retrieve initial value

function update ()                                                 --periodic function that will be called

  local current_pos = ahrs:get_position()                          --fetch the current position of the vehicle
  local home = ahrs:get_home()                                     --fetch the home position of the vehicle

  local low_batt_current_action = BATT_FS_LOW_ACT:get()            --retrieve current value

  if current_pos and home then                                     --check that both a vehicle location, and home location are available
    local distance = current_pos:get_distance(home)                --calculate the distance from home in meters

    if distance > home_radius then                                 --if outside home zone
      if low_batt_current_action != low_batt_init_action then
        --parm:set_and_save('BATT_FS_LOW_ACT', low_batt_init_action)--sets value to its initial value (slow?)
        BATT_FS_LOW_ACT:set_and_save(low_batt_init_action)         --sets value to its initial value (faster?)
        gcs:send_text(0, "LUA: enabled BATT_FS_LOW_ACT")
      end
    else                                                           --if inside home zone
      if low_batt_current_action != 0 then
        --parm:set_and_save('BATT_FS_LOW_ACT', 0)                   --sets value to 0 (do nothing) (slow?)
        BATT_FS_LOW_ACT:set_and_save(0)                            --sets value to 0 (do nothing) (faster?)
        gcs:send_text(0, "LUA: disabled BATT_FS_LOW_ACT")
      end
    end

  end

  return update, 1000                                              --request "update" to be rerun again 1000 milliseconds (1 second) from now

end

return update, 1000                                                --request "update" to be the first time 1000 milliseconds (1 second) after script is loaded

If you are landing by hand Ardupilot doesn’t know that you are landing so it tries to protect itself as configured.
Check FS_OPTIONS bit 3 continue landing on failsafe.

You can also use RTL cone to limit the climb altitude.

There are a couple of reasons why I mainly land manual, so the FS_OPTIONS is of limited help.
I was also considering the RTL-specific parameters like the cone angle but they are all of rather limited help to this problem, so a lua script is probably the best solution.

I am worried that there is a problem with
local low_batt_init_action = SCR_ENABLE:get() --retrieve that parameters value and assign to "parameter"
since it would be run every second, so it does not really store the initial value. Is there a way to run this before the update loop is started?

Put it outside the update loop. Beware, it will be run on every script initialization so it may see the parameter after it was changed. I would recommend setting the initial value either hardcoded or as a custom parameter.

Thanks for this. I just figured this out by studying some example scripts and updated my original post (fixed some errors as well). Hardcoding is probably better since I am also considering enabling the script from the GCS or RC.

Try this:


local BATT_FS_LOW_ACT = Parameter()                             --creates a parameter object
BATT_FS_LOW_ACT:init('BATT_FS_LOW_ACT')                         --get the physical location in parameters memory area so no future name search is done

local home_radius = 10                                          --define a radius around home to define a home zone for which RTL on low battery failsafe will be disabled

local low_batt_def_action = BATT_FS_LOW_ACT:get()

function update()                                                   --periodic function that will be called
    local action = low_batt_def_action

    local current_pos = ahrs:get_position()                         --fetch the current position of the vehicle
    local home = ahrs:get_home()                                    --fetch the home position of the vehicle

    if current_pos and home then                                    --check that both a vehicle location, and home location are available
        local distance = current_pos:get_distance(home)             --calculate the distance from home in meters
        if distance < home_radius then                              --if inside home zone
            action = 0
        end
    end

    BATT_FS_LOW_ACT:set(action)

    return update, 1000 --request "update" to be rerun again 1000 milliseconds (1 second) from now
end

return update, 1000

1 Like

I agree, this could work. I am just wondering whether it is a good idea to run
BATT_FS_LOW_ACT:set(action)
every second, or whether it would be better to include an additional check whether it is necessary to update it and whether it should be set_and_save() instead
Here my most recent attempt:


local low_batt_init_action = 2                                     -- better to hard code it

function update ()                                                 --periodic function that will be called

  local current_pos = ahrs:get_position()                          --fetch the current position of the vehicle
  local home = ahrs:get_home()                                     --fetch the home position of the vehicle

  local BATT_FS_LOW_ACT = Parameter()                              --creates a parameter object
  BATT_FS_LOW_ACT:init('BATT_FS_LOW_ACT')                          --get the physical location in parameters memory area so no future name search is done
  local low_batt_current_action = BATT_FS_LOW_ACT:get()            --retrieve current value

  if current_pos and home then                                     --check that both a vehicle location, and home location are available
    local distance = current_pos:get_distance(home)                --calculate the distance from home in meters

    if distance > home_radius then                                 --if outside home zone
      if low_batt_current_action ~= low_batt_init_action then
        BATT_FS_LOW_ACT:set_and_save(low_batt_init_action)         --sets value to its initial value (faster?)
        gcs:send_text(0, "LUA: enabled BATT_FS_LOW_ACT")
      end
    else                                                           --if inside home zone
      if low_batt_current_action ~= 0 then
        BATT_FS_LOW_ACT:set_and_save(0)                            --sets value to 0 (do nothing) (faster?)
        gcs:send_text(0, "LUA: disabled BATT_FS_LOW_ACT")
      end
    end

  end

  return update, 1000                                              --request "update" to be rerun again 1000 milliseconds (1 second) from now

end

return update, 1000                                                --request "update" to be the first time 1000 milliseconds (1 second) after script is loaded

You don’t want to set and save. That will save to eeprom, so the value will stick over boots. Just set will set the value until a power cycle. Setting at 1Hz is fine.

However, re-fetching the parameter by name at 1Hz is a bad idea. Get the param one time before the loop.

Thanks for the clarification. I will try my updated version sometime this week and can hopefully post a working version of the script soon.

This week I had time to test it in flight and the version below seems to work. I didn’t push the flight time to trigger a low battery failsafe (could have simply set the threshold higher), but the corresponding failsafe action changed every time I crossed the 10m radius around HOME. Here the code:

local msg_priority = 1
local home_radius = 10                                             --radius (in m) around home to define a home zone for which RTL on low battery failsafe will be disabled
local low_batt_init_action = 2                                     --hard-coded default low battery failsafe action
local tune_on='MFL16 cdef L4g'       
local tune_off='MFL16 gfed L4c'

gcs:send_text(msg_priority, "LUA: close2home_low_batt_failsafe.lua running")

local Low_Batt_FS_Act = Parameter()                                --creates a parameter object
Low_Batt_FS_Act:init('BATT_FS_LOW_ACT')                            --get the physical location in parameters memory area so no future name search is done

function update ()                                                 --periodic function that will be called

  local current_pos = ahrs:get_position()                          --fetch the current position of the vehicle
  local home = ahrs:get_home()                                     --fetch the home position of the vehicle

  local low_batt_current_action = Low_Batt_FS_Act:get()            --retrieve current value

  if current_pos and home then                                     --check that both a vehicle location, and home location are available
    local distance = current_pos:get_distance(home)                --calculate the distance from home in meters
    --gcs:send_named_float('D2H',distance)                         --just to check that distance is correct
    gcs:send_named_float('LowBattFS',low_batt_current_action)

    if distance > home_radius then                              --if outside home zone
      if low_batt_current_action ~= low_batt_init_action then
        Low_Batt_FS_Act:set(low_batt_init_action)                  --sets value to its initial value
        gcs:send_text(msg_priority, "LUA: enabled BATT_FS_LOW_ACT")
        notify:play_tune( tune_on )                                 --additional option
      end
    else                                                           --if inside home zone
      if low_batt_current_action ~= 0 then
        Low_Batt_FS_Act:set(0)                                     --sets value to 0 (do nothing)
        gcs:send_text(msg_priority, "LUA: disabled BATT_FS_LOW_ACT")
        notify:play_tune( tune_off )                                 --additional option
      end
    end

  else
--    gcs:send_named_float('D2H',0)
    gcs:send_named_float('LowBattFS',low_batt_init_action)
  end
  return update, 1000                                              --request "update" to be rerun again 1000 milliseconds (1 second) from now

end
return update, 1000                                                --request "update" to be the first time 1000 milliseconds (1 second) after script is loaded

A follow up question: Is there a way to enable specific scripts by triggering an RC switch. I know that inside the script I could use something like if rc:get_pwm(rc_channel_number) > 1500 within the script but I am wondering whether there is a more global solution.

It is possible to use Lua to manipulate what scripts are in the scripts folder though IMHO it would be better to monitor chosen switch in the code and act upon its position.