--[[---------------------------------------------------------------------------- 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