ardupilot/libraries/AP_Scripting/examples/copter-wall-climber.lua

239 lines
11 KiB
Lua

-- command a Copter to climb a wall at a specific distance
--
-- CAUTION: This script only works for Copter
-- a) waits for the vehicle to be switched to Guided mode (does nothing)
-- b) accepts pilot roll, pitch, throttle and yaw input
-- c) manual mode
-- - roll and pitch cause vehicle to fly horizontally in body frame at up to WP_SPEED
-- - yaw controls turn rate
-- - throttle controls climb/descent rate at up to PILOT_SPEED_UP/DOWN? (WP_SPEED_UP, WPNAV_SPEED_DN?)
-- d) autonomous mode
-- - roll is unchanged from manual mode
-- - yaw is unchanged from manual mode
-- - pitch controls target distance to wall (limited to no less than 2m, no more than 8m)
-- - throttle input causes vehicle to switch back to manual mode
-- - climbs at 50cm/s (need parameter?) stopping at 2m intervals
-- e) ZIGZAG_SaveWP aux switch controls switching in and out of auto mode
-- constants
local update_rate_ms = 10 -- script updates at 100hz
local update_rate_dt = update_rate_ms / 1000.0 -- update rate in seconds
local copter_guided_mode_num = 4 -- Copter's guided flight mode is mode 4
local aux_switch_function = 61 -- auxiliary switch function controlling mode. 61 is ZIGZAG_SaveWP
local climb_accel_max = 0.25 -- climb rate acceleration max in m/s/s
local climb_rate_max = 2 -- climb rate max in m/s
local climb_rate_chg_max = climb_accel_max * update_rate_dt -- max change in climb rate in a single iteration
local roll_pitch_speed_max = 2 -- horizontal speed max in m/s
local roll_pitch_accel_max = 0.25 -- horizontal acceleration max in m/s/s
local roll_pitch_speed_chg_max = roll_pitch_accel_max * update_rate_dt -- max change in roll or pitch speed in a single iteration
local rc_roll_ch = 1 -- RC input channel for roll
local rc_pitch_ch = 2 -- RC input channel for pitch
local rc_throttle_ch = 3 -- RC input channel for throttle
local rc_pwm_dz = 50 -- RC input deadzone in pwm
local wall_pitch_speed_max = roll_pitch_speed_max / 2.0 -- wall control will never overpower pilot
local wall_dist_to_speed_P = 0.5 -- P gain for converting distance to wall to forward speed
local climb_pause_sec = 3 -- pause for this many seconds during each interval
local climb_pause_counter_max = climb_pause_sec / update_rate_dt -- pause control's loop counter's initial value
-- user parmeters
local climb_dist_max = 10 -- vehicle moves to next lane at this many meters above home
local climb_stop_interval = 1 -- vehicle stops after climbing this many meters
local lane_width = 2 -- each lane is 2m apart horizontally
-- lane variables
local lane_number = 0 -- current lane number
local lane_shift_roll_speed = 1 -- vehicle moves right at this speed in m/s
local lane_shift_counter_max = lane_width / (lane_shift_roll_speed * update_rate_dt)
local lane_shift_counter = 0 -- current lane shift counter value
local lane_climb_rate = climb_rate_max / 2.0 -- autonomous climb is always 1/2 maximum
-- global variables
local aux_sw_pos_prev = 0 -- aux switch previous position
local climb_mode = 0 -- 0:manual, 1:automatic climb
local climb_rate = 0 -- current climb rate (negative is down, positive is up)
local climb_total = 0 -- total number of meters above home
local climb_interval_total = 0 -- meters climbed since last stop
local climb_pause_counter = 0 -- current pause counter value
local roll_speed = 0 -- target horizontal roll speed in m/s
local pitch_speed = 0 -- target horizontal pitch speed in m/s
local wall_dist_target = 5 -- target distance to wall
-- get the maximum speed in order to decelerate to zero within the given distance
function get_speed_max(distance, accel_max)
return math.sqrt(2.0 * math.max(0, distance) * accel_max)
end
-- the main update function that uses the takeoff and velocity controllers to fly a rough square pattern
function update()
-- reset state when disarmed
if not arming:is_armed() then
climb_mode = 0
climb_rate = 0
climb_total = 0
climb_pause_counter = 0
climb_interval_total = 0
roll_speed = 0
pitch_speed = 0
lane_number = 0
lane_shift_counter = 0
lane_climb_rate = math.abs(lane_climb_rate)
return update, update_rate_ms
end
-- if not in Guided mode do nothing
if vehicle:get_mode() ~= copter_guided_mode_num then
return update, update_rate_ms
end
-- read switch input from RCx_FUNCTION
local aux_switch = rc:find_channel_for_option(aux_switch_function)
if aux_switch then
local sw_pos = aux_switch:get_aux_switch_pos()
if (sw_pos ~= aux_sw_pos_prev) then
-- only process input if switch position changed
aux_sw_pos_prev = sw_pos
if (sw_pos == 0) then
-- pilot has selected manual mode
if (climb_mode ~= 0) then
gcs:send_text(0, "WallClimb: pilot switch to Manual")
climb_mode = 0
end
elseif (sw_pos == 2) then
-- pilot has selected auto mode
if (climb_mode ~= 1) then
gcs:send_text(0, "WallClimb: pilot switch to Auto")
climb_mode = 1
end
end
end
end
-- read normalised roll, pitch and throttle input
local roll_input = 0
local rc_roll_pwm = rc:get_pwm(rc_roll_ch)
if (rc_roll_pwm and rc_roll_pwm >= 1000 and rc_roll_pwm <= 2000 and (math.abs(rc_roll_pwm - 1500) > rc_pwm_dz)) then
roll_input = (rc_roll_pwm - 1500) / 500.0
end
local pitch_input = 0
local rc_pitch_pwm = rc:get_pwm(rc_pitch_ch)
if (rc_pitch_pwm and rc_pitch_pwm >= 1000 and rc_pitch_pwm <= 2000 and (math.abs(rc_pitch_pwm - 1500) > rc_pwm_dz)) then
pitch_input = (rc_pitch_pwm - 1500) / 500.0
end
local throttle_input = 0
local rc_throttle_pwm = rc:get_pwm(rc_throttle_ch)
if (rc_throttle_pwm and rc_throttle_pwm >= 1000 and rc_throttle_pwm <= 2000 and (math.abs(rc_throttle_pwm - 1500) > rc_pwm_dz)) then
throttle_input = (rc_throttle_pwm - 1500) / 500.0
end
-- manual mode
if (climb_mode == 0) then
-- update target climb rate
local climb_rate_target = throttle_input * climb_rate_max
climb_rate = math.min(climb_rate_target, climb_rate + climb_rate_chg_max, climb_rate_max)
climb_rate = math.max(climb_rate_target, climb_rate - climb_rate_chg_max, -climb_rate_max)
end
-- autonomous mode
local wall_pitch_speed = 0
local wall_roll_speed = 0
if (climb_mode == 1) then
-- convert rangefinder distance to pitch speed
if rangefinder:has_data_orient(0) then
local distance_m = rangefinder:distance_cm_orient(0) * 0.01
wall_pitch_speed = (distance_m - wall_dist_target) * wall_dist_to_speed_P
wall_pitch_speed = math.min(wall_pitch_speed, wall_pitch_speed_max)
wall_pitch_speed = math.max(wall_pitch_speed, -wall_pitch_speed_max)
end
-- switch to manual if pilot provides throttle input
if (math.abs(throttle_input) ~= 0) then
gcs:send_text(0, "WallClimb: throttle input, switch to Manual")
climb_mode = 0
else
-- update distance climbed
local climb_chg = climb_rate * update_rate_dt
climb_interval_total = climb_interval_total + climb_chg
climb_total = climb_total + climb_chg
-- determine if we should pause
local dist_to_interval = climb_stop_interval - math.abs(climb_interval_total)
if (dist_to_interval <= 0) then
gcs:send_text(0, "WallClimb: pausing at " .. string.format("%3.1f", climb_total) .. "m")
climb_pause_counter = climb_pause_counter_max
climb_interval_total = 0
-- determine if we should move to next lane
if ((climb_total > climb_dist_max) or (climb_total <= 0)) then
lane_number = lane_number + 1
lane_climb_rate = -lane_climb_rate
lane_shift_counter = lane_shift_counter_max
gcs:send_text(0, "WallClimb: starting lane " .. tostring(lane_number))
end
end
-- default target climb rate to lane climb rate
local climb_rate_target = lane_climb_rate
-- limit target speed so vehicle can stop before next interval
-- speed limit is always at least 0.1m/s so copter doesn't get stuck near interval
local speed_max = math.max(get_speed_max(dist_to_interval, climb_accel_max), 0.1)
climb_rate_target = math.max(climb_rate_target, -speed_max)
climb_rate_target = math.min(climb_rate_target, speed_max)
-- if paused set target climb rate to zero
if (climb_pause_counter > 0) then
climb_pause_counter = climb_pause_counter - 1
climb_rate_target = 0
if (climb_pause_counter == 0) then
gcs:send_text(0, "WallClimb: restarting")
end
end
-- if shifting lanes pause climb and roll right
if (lane_shift_counter > 0) then
lane_shift_counter = lane_shift_counter - 1
wall_roll_speed = lane_shift_roll_speed
climb_rate_target = 0
-- if completing lane shift trigger another pause
if (lane_shift_counter == 0) then
climb_pause_counter = climb_pause_counter_max
end
end
-- calculate acceleration limited climb rate
if (climb_rate_target >= climb_rate) then
climb_rate = math.min(climb_rate_target, climb_rate + climb_rate_chg_max, climb_rate_max)
else
climb_rate = math.max(climb_rate_target, climb_rate - climb_rate_chg_max, -climb_rate_max)
end
end
end
-- update target roll using both pilot and autonomous control (+ve right, -ve left)
local roll_speed_target = roll_input * roll_pitch_speed_max + wall_roll_speed
roll_speed = math.min(roll_speed_target, roll_speed + roll_pitch_speed_chg_max, roll_pitch_speed_max)
roll_speed = math.max(roll_speed_target, roll_speed - roll_pitch_speed_chg_max, -roll_pitch_speed_max)
-- update target pitch using both pilot and autonomous control (+ve forward, -ve backwards)
local pitch_speed_target = -pitch_input * roll_pitch_speed_max + wall_pitch_speed
pitch_speed = math.min(pitch_speed_target, pitch_speed + roll_pitch_speed_chg_max, roll_pitch_speed_max)
pitch_speed = math.max(pitch_speed_target, pitch_speed - roll_pitch_speed_chg_max, -roll_pitch_speed_max)
-- convert targets from body to earth frame and send to guided mode velocity controller
local yaw_rad = ahrs:get_yaw()
yaw_cos = math.cos(yaw_rad)
yaw_sin = math.sin(yaw_rad)
local target_vel_ned = Vector3f()
target_vel_ned:x(pitch_speed * yaw_cos - roll_speed * yaw_sin)
target_vel_ned:y(pitch_speed * yaw_sin + roll_speed * yaw_cos)
target_vel_ned:z(-climb_rate)
if not (vehicle:set_target_velocity_NED(target_vel_ned)) then
gcs:send_text(0, "failed to execute velocity command")
end
return update, update_rate_ms
end
return update()