mirror of https://github.com/ArduPilot/ardupilot
210 lines
7.6 KiB
Lua
210 lines
7.6 KiB
Lua
--[[----------------------------------------------------------------------------
|
|
|
|
TerrainDetector ArduPilot Lua script
|
|
|
|
Uses gyro and accelerometer data to detect rough terrain and slow the
|
|
vehicle's speed accordingly during auto missions.
|
|
|
|
Provides a set of custom tuning parameters:
|
|
ROUGH_SPEED : (m/s) rough terrain desired speed, set to -1 to disable detection
|
|
ROUGH_TIMEOUT_MS: (ms) min time to remain slowed down for rough terrain
|
|
ROUGH_GZ_MAX : (G) slow down on Gz impulses greater than this value
|
|
ROUGH_RATE_MAX : (deg/s) slow down on rate transients greater than this value
|
|
|
|
These gain values are used for smooth terrain detection.
|
|
Smaller values (less than 1.0) are more restrictive about resuming normal speed.
|
|
ROUGH_GZ_GAIN : Gz impulse gain
|
|
ROUGH_RATE_GAIN : gyro transient rate gain
|
|
|
|
CAUTION: This script is capable of engaging and disengaging autonomous control
|
|
of a vehicle. Use this script AT YOUR OWN RISK.
|
|
|
|
-- Yuri -- Jul 2022
|
|
|
|
LICENSE - GNU GPLv3 https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
|
|
Concept first presented during this live stream:
|
|
https://www.youtube.com/watch?v=UdXGXjigxAo&t=7155s
|
|
Credit to @ktrussell for the idea and discussion!
|
|
------------------------------------------------------------------------------]]
|
|
|
|
local SCRIPT_NAME = 'TerrainDetector'
|
|
local RUN_INTERVAL_MS = 25 -- needs to be pretty fast for good detection
|
|
local SBY_INTERVAL_MS = 500 -- slower interval when detection is disabled
|
|
local PARAM_TABLE_KEY = 117 -- unique index value between 0 and 200
|
|
|
|
-- GCS messages
|
|
local VERBOSE_MODE = 1 -- 0 to suppress all GCS messages,
|
|
-- 1 for normal status messages
|
|
-- 2 for additional debug messages
|
|
local MSG_NORMAL = 1
|
|
local MSG_DEBUG = 2
|
|
|
|
-- MAVLink values
|
|
local MAV_SEVERITY_WARNING = 4
|
|
local MAV_SEVERITY_INFO = 6
|
|
|
|
-- mathematical/physical constants
|
|
local G = -9.81 -- m/s/s
|
|
|
|
-- create custom parameter set
|
|
local function add_params(key, prefix, tbl)
|
|
assert(param:add_table(key, prefix, #tbl), string.format('Could not add %s param table.', prefix))
|
|
for num = 1, #tbl do
|
|
assert(param:add_param(key, num, tbl[num][1], tbl[num][2]), string.format('Could not add %s%s.', prefix, tbl[num][1]))
|
|
end
|
|
end
|
|
|
|
add_params(PARAM_TABLE_KEY, 'ROUGH_', {
|
|
-- { name, default value },
|
|
{ 'SPEED', 0.7 },
|
|
{ 'GZ_MAX', 1.33 },
|
|
{ 'RATE_MAX', 28 },
|
|
{ 'GZ_GAIN', 0.9 },
|
|
{ 'RATE_GAIN', 0.8 },
|
|
{ 'TIMEOUT_MS', 7500 }
|
|
})
|
|
|
|
-- wrapper for gcs:send_text()
|
|
local function gcs_msg(msg_type, severity, txt)
|
|
if type(msg_type) == 'string' then
|
|
-- allow just a string to be passed for simple/routine messages
|
|
txt = msg_type
|
|
msg_type = MSG_NORMAL
|
|
severity = MAV_SEVERITY_INFO
|
|
end
|
|
if type(severity) == 'string' then
|
|
-- allow just severity and string to be passed for normal messages
|
|
txt = severity
|
|
severity = msg_type
|
|
msg_type = MSG_NORMAL
|
|
end
|
|
if msg_type <= VERBOSE_MODE then
|
|
gcs:send_text(severity, string.format('%s: %s', SCRIPT_NAME, txt))
|
|
end
|
|
end
|
|
|
|
local last_g_z = 0 -- to calculate impulse Gz
|
|
local timeout_ms = 0
|
|
|
|
-- debug values
|
|
local max_g_z = 0
|
|
local max_gyro_rate = 0
|
|
|
|
local wp_speed_normal = nil
|
|
local wp_speed_rough = nil
|
|
local impulse_gz_threshold = nil
|
|
local gyro_rate_threshold = nil
|
|
local rough_terrain_timeout_ms = nil
|
|
local impulse_gain = nil
|
|
local gyro_gain = nil
|
|
|
|
function standby()
|
|
wp_speed_rough = param:get('ROUGH_SPEED')
|
|
if wp_speed_rough < 0 then return standby, SBY_INTERVAL_MS end
|
|
|
|
if mission:state() == mission.MISSION_RUNNING then
|
|
-- only poll remaining parameters at start of mission
|
|
wp_speed_normal = param:get('WP_SPEED')
|
|
impulse_gz_threshold = param:get('ROUGH_GZ_MAX')
|
|
gyro_rate_threshold = param:get('ROUGH_RATE_MAX')
|
|
rough_terrain_timeout_ms = param:get('ROUGH_TIMEOUT_MS')
|
|
impulse_gain = param:get('ROUGH_GZ_GAIN')
|
|
gyro_gain = param:get('ROUGH_RATE_GAIN')
|
|
-- if ROUGH_SPEED param not set or invalid, use half of WP_SPEED
|
|
if wp_speed_rough == 0 or wp_speed_rough > wp_speed_normal then
|
|
wp_speed_rough = wp_speed_normal / 2
|
|
gcs_msg(MAV_SEVERITY_WARNING, 'ROUGH_SPEED invalid, using half WP_SPEED')
|
|
end
|
|
|
|
last_g_z = 0
|
|
return do_normal_speed, RUN_INTERVAL_MS
|
|
end
|
|
return standby, SBY_INTERVAL_MS
|
|
end
|
|
|
|
function initiate_rough_speed()
|
|
gcs_msg(MSG_NORMAL, MAV_SEVERITY_WARNING, 'Slowing for rough terrain')
|
|
vehicle:set_desired_speed(wp_speed_rough)
|
|
last_g_z = 0
|
|
timeout_ms = millis() + rough_terrain_timeout_ms
|
|
max_g_z = 0
|
|
max_gyro_rate = 0
|
|
return do_rough_speed, RUN_INTERVAL_MS
|
|
end
|
|
|
|
function initiate_normal_speed()
|
|
gcs_msg(MSG_NORMAL, MAV_SEVERITY_WARNING, 'Resuming normal speed')
|
|
vehicle:set_desired_speed(wp_speed_normal)
|
|
last_g_z = 0
|
|
return do_normal_speed, RUN_INTERVAL_MS
|
|
end
|
|
|
|
function do_normal_speed()
|
|
if mission:state() ~= mission.MISSION_RUNNING then
|
|
return standby, SBY_INTERVAL_MS
|
|
end
|
|
|
|
local g_z = ahrs:get_accel():z() / G -- convert to G from m/s/s
|
|
local gyro = ahrs:get_gyro()
|
|
-- sample max of roll/pitch rates
|
|
local gyro_rate = math.deg(math.max(math.abs(gyro:x()), math.abs(gyro:y())))
|
|
local impulse_g_z = math.abs(g_z - last_g_z) -- sample delta is the measured impulse
|
|
last_g_z = g_z
|
|
|
|
if impulse_g_z > impulse_gz_threshold then -- slow down
|
|
gcs_msg(MSG_DEBUG, MAV_SEVERITY_INFO, string.format('Impulse - %.2f Gz', impulse_g_z))
|
|
return initiate_rough_speed, RUN_INTERVAL_MS
|
|
end
|
|
|
|
if gyro_rate > gyro_rate_threshold then -- slow down
|
|
gcs_msg(MSG_DEBUG, MAV_SEVERITY_INFO, string.format('Transient - %.2f deg/s', gyro_rate))
|
|
return initiate_rough_speed, RUN_INTERVAL_MS
|
|
end
|
|
|
|
return do_normal_speed, RUN_INTERVAL_MS
|
|
end
|
|
|
|
function do_rough_speed()
|
|
if mission:state() ~= mission.MISSION_RUNNING then
|
|
return standby, SBY_INTERVAL_MS
|
|
end
|
|
|
|
local g_z = ahrs:get_accel():z() / G -- convert to G from m/s/s
|
|
local gyro = ahrs:get_gyro()
|
|
-- sample max of roll/pitch rates
|
|
local gyro_rate = math.deg(math.max(math.abs(gyro:x()), math.abs(gyro:y())))
|
|
local impulse_g_z = math.abs(g_z - last_g_z) -- sample delta is the measured impulse
|
|
last_g_z = g_z
|
|
|
|
max_g_z = math.max(g_z, max_g_z)
|
|
max_gyro_rate = math.max(gyro_rate, max_gyro_rate)
|
|
|
|
local now = millis()
|
|
|
|
if impulse_g_z > impulse_gz_threshold * impulse_gain then -- stay slow
|
|
gcs_msg(MSG_DEBUG, MAV_SEVERITY_INFO, string.format('Timer reset - %.2fGz', impulse_g_z))
|
|
max_g_z = 0
|
|
timeout_ms = now + rough_terrain_timeout_ms
|
|
return do_rough_speed, RUN_INTERVAL_MS
|
|
end
|
|
|
|
if gyro_rate > gyro_rate_threshold * gyro_gain then -- stay slow
|
|
gcs_msg(MSG_DEBUG, MAV_SEVERITY_INFO, string.format('Timer reset - %.2f deg/s', gyro_rate))
|
|
max_gyro_rate = 0
|
|
timeout_ms = now + rough_terrain_timeout_ms
|
|
return do_rough_speed, RUN_INTERVAL_MS
|
|
end
|
|
|
|
if now > timeout_ms then -- no longer in rough terrain
|
|
gcs_msg(MSG_DEBUG, MAV_SEVERITY_INFO, string.format('Max - %.2fGz ... %.2f deg/s', max_g_z, max_gyro_rate))
|
|
return initiate_normal_speed, RUN_INTERVAL_MS
|
|
end
|
|
|
|
return do_rough_speed, RUN_INTERVAL_MS
|
|
end
|
|
|
|
gcs_msg('Script active')
|
|
|
|
return standby, SBY_INTERVAL_MS
|