You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
7.6 KiB
209 lines
7.6 KiB
--[[---------------------------------------------------------------------------- |
|
|
|
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
|
|
|