AP_Scripting:add rate based aerobatics

This commit is contained in:
Henry Wurzburg 2022-12-26 16:02:40 -06:00 committed by Andrew Tridgell
parent 0db6146f60
commit d6a5d31625
2 changed files with 836 additions and 0 deletions

View File

@ -0,0 +1,145 @@
# Sport Aerobatics
The lua script "sport_aerobatics.lua" implements scripted aerobatics,
allowing fixed wing aircraft to execute a number of aerobatic
manoeuvres either in AUTO mission or by triggering using pilot commands
using RC switches.
As always, but particularly with scripted aerobatics, test in SITL until
you understand the function and behaviour of each manouver. You will need
an appropriate aircraft, and be ready to take manual control if necessary!
These tricks are rate-based vs the precision trajectory-based manouvers of the script in the TrajectoryBased directory above this one. However, many planes have difficulty completing those tricks unless they can sustain extended vertical climbs and knife-edges, especially in wind. These tricks do not try to maintain a geospatial track, but rather, just attitude rates. Even planes that cannot hold the 90 deg knife edge trick, will probably hold the 180 deg (inverted) one and do loops and rolls, and these tricks can be done in even strong wind, although will not be well shaped.
## Available Manoeuvres
The following table gives the available manoeuvres. Each manoeuvre has
an ID number which is used in the AUTO mission or in the TRIKn_ID
parameters (described below). The present ground track is used as the track for the trick.
The "Turnaround" column indicates if the manoeuvre results in a course reversal, which impacts how it is used in
AUTO missions. Once the trick is completed, the mode that was being used at the start of the trick is restored. If the mode is CRUISE, its
track and altitude are reset to the values present when the mode is restored. Tricks in AUTO missions require that they be performed between two waypoints to establish
the ground track.
| ID | Name | Arg1 | Arg2 | Turnaround |
| -- | ------------------------ | ------------_-- | -------------------------- | -----------|
| 1 | Roll(s) | rollrate(dps) | num rolls | No |
| 2 | Loop(s)/TurnAround | pitchrate(dps) | num loops or turnaround(0) | if num=0 |
| 3 | Rolling Circle | yawrate(dps) | rollrate(dps) | No |
| 4 | KnifeEdge | roll angle(deg) | length(sec) | No |
| 5 | Pause | length(sec) | na | No |
note: for Rolling Circle, the time it takes to make the circle is 360/yawrate. You should make sure that an integer number of rolls is commanded by the rollrate parameter in that time, ie rollrate should be set to the number of rolls * yawrate.
## Loading the script
Put the sport_aerobatics.lua script on your microSD card in the
APM/SCRIPTS directory. You can use MAVFtp to do this.
Then set
- SCR_ENABLE = 1
- SCR_HEAP_SIZE = 200000
- SCR_VM_I_COUNT = 100000
You will need to refresh parameters after setting SCR_ENABLE. Then
reboot to start scripting.
## Aircraft Setup
The aircraft needs to be setup to perform well in ACRO mode. You need
to enable the yaw rate controller by setting if it has a rudder (even if it cannot hold a sustained 90 degree knife-edge:
- YAW_RATE_ENABLE = 1
- ACRO_YAW_RATE = 90
The ACRO_YAW_RATE depends on the capabilities of the aircraft, but
around 90 degrees/second is reasonable.
You need to tune the yaw rate controller by flying in AUTOTUNE mode
and moving the rudder stick from side to side until the yaw rate
tuning is reported as being finished. For optimal results, log examination and manual adjustment
of axis tuning will sometimes be required, especially pitch and yaw tuning in order to get precise rolling circles. A future video on this optimization will be produced and posted in the ArduPilot YouTube channel.
## Use In AUTO Missions
To use in an AUTO mission you can create waypoint missions containing
NAV_SCRIPT_TIME elements (shown as SCRIPT_TIME in MissionPlanner). These mission items take the following arguments:
- the command ID from the table above
- the timeout in seconds
- up to four arguments as shown in the above table
The aerobatics system will use the location of the previous and next
waypoints to line up the manoeuvre. You need to plan a normal
waypoint just before the location where you want to start the
manoeuvre, then the NAV_SCRIPT_TIME with the trick or schedule ID, a timeout that is long enough to allow the trick, and then a normal waypoint after the manoeuvre.
## Use with "tricks on a switch"
You can trigger the manoeuvres using RC switches, allowing you to have
up to 11 tricks pre-programmed on your transmitter ready for use in
fixed wing flight. You can trigger the tricks in the following flight
modes:
- CIRCLE
- STABILIZE
- ACRO
- FBWA
- FBWB
- CRUISE
- LOITER
Set TRIK_COUNT to the number of tricks you want to make available and reboot,
with a maximum of 11. (Why 11 when fewer than that are available? To allow variants, such as different knife-edges, ie 90 deg and 180 deg inveterd flight)
After setting TRIK_COUNT, reboot and refresh parameters. You will find
you will now have 5 parameters per trick.
- TRIKn_ID
- TRIKn_ARG1
- TRIKn_ARG2
- TRIKn_ARG3 (unused, future use)
- TRIKn_ARG4 (unused, future use)
The ID parameter is the manoeuvre from the above table, and the arguments are the arguments to each manoeuvre.
Note: these parameters, if being loaded from a file, will not be present until scripting is enabled, the the LUA script has been
actually run, since they are created via the script, not the firmware.
Now you need to setup your two control channels. You should have one 3
position switch for activating tricks, and one knob or switch to
select tricks. It is best to use a knob for the trick selector so you can have up to 11 tricks.
Work out which RC input channel you want to use for activation (a 3 position switch) and set
- RCn_OPTION = 300
Then work out what RC input channel you want to use for selection and set
- RCn_OPTION = 301
## Flying with tricks
When the activation channel (the one marked with option 300) is in the
middle position then when you move the knob the GCS will display the
currently selected trick.
To activate a trick you move the activation channel to the top
position, and the trick will run.
Moving the activation switch to the bottom position cancels any
running trick and stops the trick system.
Changing flight modes will also cancel any active trick.
## Parameters
There are a number of parameters added by this script to control its control loops. The defaults should be satisfactory, but some of the key parameters are:
- AEROM_HGT_KE_BIAS: knife-edge boost. Adds immediate rudder as the plane rolls into 90 degree positions rather than waiting of an altitude change
- AEROM_THR_FF : modulates throttle as pitch increases or decreases
the other parameters control the height and speed PID controllers used in the script

View File

@ -0,0 +1,691 @@
--[[ perform simple aerobatic manoeuvres in AUTO mode
cmd = 1: axial rolls, arg1 = roll rate dps, arg2 = number of rolls
cmd = 2: loops or 180deg return, arg1 = pitch rate dps, arg2 = number of loops, if zero do a 1/2 cuban8-like return
cmd = 3: rolling circle, arg1 = yaw rate, arg2 = roll rate
cmd = 4: knife edge at any angle, arg1 = roll angle to hold, arg2 = duration
cmd = 5: pause, holding heading and alt to allow stabilization after a move, arg1 = duration in seconds
]]--
DO_JUMP = 177
k_throttle = 70
MODE_AUTO = 10
-- setup param block for aerobatics, reserving 30 params beginning with AERO_
local PARAM_TABLE_KEY = 72
local PARAM_TABLE_PREFIX = "AEROR_"
assert(param:add_table(PARAM_TABLE_KEY, "AEROR_", 30), 'could not add param table')
-- add a parameter and bind it to a variable
function bind_add_param(name, idx, default_value)
assert(param:add_param(PARAM_TABLE_KEY, idx, name, default_value), string.format('could not add param %s', name))
return Parameter(PARAM_TABLE_PREFIX .. name)
end
local HGT_P = bind_add_param('HGT_P',1,1) -- height P gain
local HGT_I = bind_add_param("HGT_I",2,1) -- height I gain
local HGT_KE_BIAS = bind_add_param("KE_ADD",3,20) -- height knifeedge addition for pitch
local THR_PIT_FF = bind_add_param("PIT_FF",4,80) -- throttle FF from pitch
local SPD_P = bind_add_param("SPD_P",5,5) -- speed P gain
local SPD_I = bind_add_param("SPD_I",6,25) -- speed I gain
local TRIM_THROTTLE = Parameter("TRIM_THROTTLE")
local TRIM_ARSPD_CM = Parameter("TRIM_ARSPD_CM")
local RLL2SRV_TCONST = Parameter("RLL2SRV_TCONST")
local PITCH_TCONST = Parameter("PTCH2SRV_TCONST")
local last_roll_err = 0.0
local last_id = 0
local initial_yaw_deg = 0
local wp_yaw_deg = 0
local initial_height = 0
local repeat_count = 0
local running = false
local roll_stage = 0
local function TrickDef(id, arg1, arg2, arg3, arg4)
local self = {}
self.id = id
self.args = {arg1, arg2, arg3, arg4}
return self
end
--[[
Aerobatic tricks on a switch support - allows for tricks to be initiated outside AUTO mode
--]]
-- 2nd param table for tricks on a switch
local PARAM_TABLE_KEY2 = 73
local PARAM_TABLE_PREFIX2 = "TRIK"
assert(param:add_table(PARAM_TABLE_KEY2, PARAM_TABLE_PREFIX2, 63), 'could not add param table2')
-- add a parameter and bind it to a variable in table2
function bind_add_param2(name, idx, default_value)
assert(param:add_param(PARAM_TABLE_KEY2, idx, name, default_value), string.format('could not add param %s', name))
return Parameter(PARAM_TABLE_PREFIX2 .. name)
end
local TRICKS = nil
local TRIK_SEL_FN = nil
local TRIK_ACT_FN = nil
local TRIK_COUNT = nil
-- constrain a value between limits
function constrain(v, vmin, vmax)
if v < vmin then
v = vmin
end
if v > vmax then
v = vmax
end
return v
end
-- setup parameters for tricks
TRIK_SEL_FN = bind_add_param2("_SEL_FN", 2, 301)
TRIK_ACT_FN = bind_add_param2("_ACT_FN", 3, 300)
TRIK_COUNT = bind_add_param2("_COUNT", 4, 3)
TRICKS = {}
-- setup parameters for tricks
count = math.floor(TRIK_COUNT:get())
count = constrain(count,1,11)
if count > 11 then
count = 1
end
for i = 1, count do
local k = 5*i
local prefix = string.format("%u", i)
TRICKS[i] = TrickDef(bind_add_param2(prefix .. "_ID", k+0, i),
bind_add_param2(prefix .. "_ARG1", k+1, 30),
bind_add_param2(prefix .. "_ARG2", k+2, 0),
bind_add_param2(prefix .. "_ARG3", k+3, 0),
bind_add_param2(prefix .. "_ARG4", k+4, 0))
end
gcs:send_text(5, string.format("Enabled %u aerobatic tricks", TRIK_COUNT:get()))
-- roll angle error 180 wrap to cope with errors while in inverted segments
function roll_angle_error_wrap(roll_angle_error)
if math.abs(roll_angle_error) > 180 then
if roll_angle_error > 0 then
roll_angle_error = roll_angle_error - 360
else
roll_angle_error= roll_angle_error +360
end
end
return roll_angle_error
end
--roll controller to keep wings level in earth frame. if arg is 0 then level is at only 0 deg, otherwise its at 180/-180 roll also for loops
function earth_frame_wings_level(arg)
local roll_deg = math.deg(ahrs:get_roll())
local roll_angle_error = 0.0
if (roll_deg > 90 or roll_deg < -90) and arg ~= 0 then
roll_angle_error = 180 - roll_deg
else
roll_angle_error = - roll_deg
end
return roll_angle_error_wrap(roll_angle_error)/(RLL2SRV_TCONST:get())
end
function wrap_360(angle)
local res = math.fmod(angle, 360.0)
if res < 0 then
res = res + 360.0
end
return res
end
function wrap_180(angle)
local res = wrap_360(angle)
if res > 180 then
res = res - 360
end
return res
end
-- a PI controller implemented as a Lua object
local function PI_controller(kP,kI,iMax)
-- the new instance. You can put public variables inside this self
-- declaration if you want to
local self = {}
-- private fields as locals
local _kP = kP or 0.0
local _kI = kI or 0.0
local _kD = kD or 0.0
local _iMax = iMax
local _last_t = nil
local _I = 0
local _P = 0
local _total = 0
local _counter = 0
local _target = 0
local _current = 0
-- update the controller.
function self.update(target, current)
local now = millis():tofloat() * 0.001
if not _last_t then
_last_t = now
end
local dt = now - _last_t
_last_t = now
local err = target - current
_counter = _counter + 1
local P = _kP * err
_I = _I + _kI * err * dt
if _iMax then
_I = constrain(_I, -_iMax, iMax)
end
local I = _I
local ret = P + I
_target = target
_current = current
_P = P
_total = ret
return ret
end
-- reset integrator to an initial value
function self.reset(integrator)
_I = integrator
end
function self.set_I(I)
_kI = I
end
function self.set_P(P)
_kP = P
end
function self.set_Imax(Imax)
_iMax = Imax
end
-- log the controller internals
function self.log(name, add_total)
-- allow for an external addition to total
logger.write(name,'Targ,Curr,P,I,Total,Add','ffffff',_target,_current,_P,_I,_total,add_total)
end
-- return the instance
return self
end
local function height_controller(kP_param,kI_param,KnifeEdge_param,Imax)
local self = {}
local kP = kP_param
local kI = kI_param
local KnifeEdge = KnifeEdge_param
local PI = PI_controller(kP:get(), kI:get(), Imax)
function self.update(target)
local target_pitch = PI.update(target, ahrs:get_position():alt()*0.01)
local roll_rad = ahrs:get_roll()
local ke_add = math.abs(math.sin(roll_rad)) * KnifeEdge:get()
target_pitch = target_pitch + ke_add
PI.log("HPI", ke_add)
return target_pitch
end
function self.reset()
PI.reset(math.max(math.deg(ahrs:get_pitch()), 3.0))
PI.set_P(kP:get())
PI.set_I(kI:get())
end
return self
end
local height_PI = height_controller(HGT_P, HGT_I, HGT_KE_BIAS, 20.0)
local speed_PI = PI_controller(SPD_P:get(), SPD_I:get(), 100.0)
-- a controller to target a zero pitch angle and zero heading change, used in a roll
-- output is a body frame pitch rate, with convergence over time tconst in seconds
function pitch_controller(target_pitch_deg, target_yaw_deg, tconst)
local roll_deg = math.deg(ahrs:get_roll())
local pitch_deg = math.deg(ahrs:get_pitch())
local yaw_deg = math.deg(ahrs:get_yaw())
-- get earth frame pitch and yaw rates
local ef_pitch_rate = (target_pitch_deg - pitch_deg) / tconst
local ef_yaw_rate = wrap_180(target_yaw_deg - yaw_deg) / tconst
local bf_pitch_rate = math.sin(math.rad(roll_deg)) * ef_yaw_rate + math.cos(math.rad(roll_deg)) * ef_pitch_rate
local bf_yaw_rate = math.cos(math.rad(roll_deg)) * ef_yaw_rate - math.sin(math.rad(roll_deg)) * ef_pitch_rate
return bf_pitch_rate, bf_yaw_rate
end
-- a controller for throttle to account for pitch
function throttle_controller()
local pitch_rad = ahrs:get_pitch()
local thr_ff = THR_PIT_FF:get()
local throttle = TRIM_THROTTLE:get() + math.sin(pitch_rad) * thr_ff
return constrain(throttle, 0, 100.0)
end
-- recover entry altitude
function recover_alt()
local target_pitch = height_PI.update(initial_height)
local pitch_rate, yaw_rate = pitch_controller(target_pitch, wp_yaw_deg, PITCH_TCONST:get())
return target_pitch, pitch_rate, yaw_rate
end
-- start of trick routines---------------------------------------------------------------------------------
function do_axial_roll(arg1, arg2)
-- constant roll rate axial roll, arg1 roll rate, arg2 is number of rolls
if not running then
running = true
roll_num = 1
repeat_count = arg2 -1
roll_stage = 0
height_PI.reset()
--gcs:send_text(5, string.format("Starting roll(s)"))
end
local roll_rate = arg1
local pitch_deg = math.deg(ahrs:get_pitch())
local roll_deg = math.deg(ahrs:get_roll())
if roll_stage == 0 then
if roll_deg > 45 then
roll_stage = 1
end
elseif roll_stage == 1 then
if roll_deg > -5 and roll_deg < 5 then
-- we're done with a roll
gcs:send_text(5, string.format("Finished roll %d", roll_num))
if repeat_count > 0 then
roll_stage = 0
repeat_count = repeat_count - 1
roll_num = roll_num + 1
else
running = false
if vehicle:get_mode() == MODE_AUTO then
vehicle:nav_script_time_done(last_id)
else
vehicle:nav_scripting_enable(255)
end
roll_stage = 2
return
end
end
end
if roll_stage < 2 then
throttle = throttle_controller()
target_pitch = height_PI.update(initial_height)
pitch_rate, yaw_rate = pitch_controller(target_pitch, wp_yaw_deg, PITCH_TCONST:get())
vehicle:set_target_throttle_rate_rpy(throttle, roll_rate, pitch_rate, yaw_rate)
end
end
local loop_stage = 0
local target_vel
function do_loop(arg1, arg2)
-- do one loop with controllable pitch rate arg1 is pitch rate, arg2 number of loops, 0 indicates 1/2 cuban8 reversal style turnaround
if not running then
running = true
loop_stage = 0
loop_number = 1
repeat_count = arg2 -1
target_vel = ahrs:get_velocity_NED():length()
if arg2 ~=0 then
gcs:send_text(5, string.format("Starting loop"))
else
gcs:send_text(5, string.format("Starting turnaround"))
end
end
local throttle = throttle_controller()
local pitch_deg = math.deg(ahrs:get_pitch())
local roll_deg = math.deg(ahrs:get_roll())
local vel = ahrs:get_velocity_NED():length()
local pitch_rate = arg1
local pitch_rate = pitch_rate * (1+ 2*((vel/target_vel)-1)) --increase/decrease rate based on velocity to round loop
pitch_rate = constrain(pitch_rate,.5 * arg1, 3 * arg1)
if loop_stage == 0 then
if pitch_deg > 60 then
loop_stage = 1
end
elseif loop_stage == 1 then
if (math.abs(roll_deg) < 90 and pitch_deg > -5 and pitch_deg < 5 and repeat_count >= 0) then
-- we're done with loop
gcs:send_text(5, string.format("Finished loop %d", loop_number))
loop_stage = 2 --now recover stage
height_PI.reset()
elseif (math.abs(roll_deg) > 90 and pitch_deg > -5 and pitch_deg < 5 and repeat_count < 0) then
gcs:send_text(5, string.format("Finished turnaround "))
loop_stage = 2 --now recover stage
height_PI.reset()
end
elseif loop_stage == 2 then
-- recover alt if above or below start and terminate
if math.abs(ahrs:get_position():alt()*0.01 - initial_height) > 3 then
throttle, pitch_rate, yaw_rate = recover_alt()
elseif repeat_count > 0 then
loop_stage = 0
repeat_count = repeat_count - 1
loop_number = loop_number + 1
else
running = false
if vehicle:get_mode() == MODE_AUTO then
vehicle:nav_script_time_done(last_id)
else
vehicle:nav_scripting_enable(255)
end
return
end
end
throttle = throttle_controller()
if loop_stage == 2 or loop_stage == 0 then
level_type = 0
else
level_type = 1
end
if math.abs(pitch_deg) > 85 and math.abs(pitch_deg) < 95 then
roll_rate = 0
else
roll_rate = earth_frame_wings_level(level_type)
end
vehicle:set_target_throttle_rate_rpy(throttle, roll_rate, pitch_rate, 0)
end
local rolling_circle_stage = 0
local rolling_circle_yaw = 0
local rolling_circle_last_ms = 0
function do_rolling_circle(arg1, arg2)
-- constant roll rate circle roll, arg1 yaw rate, positive to right, neg to left, arg2 is roll rate
if not running then
running = true
rolling_circle_stage = 0
rolling_circle_yaw_deg = 0
rolling_circle_last_ms = millis()
height_PI.reset()
--gcs:send_text(5, string.format("Starting rolling circle"))
end
local yaw_rate_dps = arg1
local roll_rate_dps = arg2
local pitch_deg = math.deg(ahrs:get_pitch())
local roll_deg = math.deg(ahrs:get_roll())
local yaw_deg = math.deg(ahrs:get_yaw())
local now_ms = millis()
local dt = (now_ms - rolling_circle_last_ms):tofloat() * 0.001
rolling_circle_last_ms = now_ms
rolling_circle_yaw_deg = rolling_circle_yaw_deg + yaw_rate_dps * dt
if rolling_circle_stage == 0 then
if math.abs(rolling_circle_yaw_deg) > 10.0 then
rolling_circle_stage = 1
end
elseif rolling_circle_stage == 1 then
if math.abs(rolling_circle_yaw_deg) >= 360.0 then
running = false
-- we're done
gcs:send_text(5,"Finished rolling circle")
if vehicle:get_mode() == MODE_AUTO then
vehicle:nav_script_time_done(last_id)
else
vehicle:nav_scripting_enable(255)
end
rolling_circle_stage = 2
return
end
end
if rolling_circle_stage < 2 then
target_pitch = height_PI.update(initial_height)
vel = ahrs:get_velocity_NED()
pitch_rate, yaw_rate = pitch_controller(target_pitch, wrap_360(rolling_circle_yaw_deg+initial_yaw_deg), PITCH_TCONST:get())
throttle = throttle_controller()
throttle = constrain(throttle, 0, 100.0)
vehicle:set_target_throttle_rate_rpy(throttle, roll_rate_dps, pitch_rate, yaw_rate)
end
end
local knife_edge_ms = 0
function do_knife_edge(arg1,arg2)
-- arg1 is angle +/-180, duration is arg2
local now = millis():tofloat() * 0.001
if not running then
running = true
height_PI.reset()
knife_edge_s = now
gcs:send_text(5, string.format("%d Knife edge", arg1))
end
local i=0
if (now - knife_edge_s) < arg2 then
local roll_deg = math.deg(ahrs:get_roll())
local roll_angle_error = (arg1 - roll_deg)
if math.abs(roll_angle_error) > 180 then
if roll_angle_error > 0 then
roll_angle_error = roll_angle_error - 360
else
roll_angle_error= roll_angle_error +360
end
end
roll_rate = roll_angle_error/RLL2SRV_TCONST:get()
target_pitch = height_PI.update(initial_height)
pitch_rate, yaw_rate = pitch_controller(target_pitch, wp_yaw_deg, PITCH_TCONST:get())
throttle = throttle_controller()
vehicle:set_target_throttle_rate_rpy(throttle, roll_rate, pitch_rate, yaw_rate)
else
gcs:send_text(5, string.format("Finished Knife edge", arg1))
running = false
if vehicle:get_mode() == MODE_AUTO then
vehicle:nav_script_time_done(last_id)
else
vehicle:nav_scripting_enable(255)
end
return
end
end
-- fly level for a time..allows full altitude recovery after trick
function do_pause(arg1,arg2)
-- arg1 is time of pause in sec, arg2 is unused
local now = millis():tofloat() * 0.001
if not running then
running = true
height_PI.reset()
knife_edge_s = now
gcs:send_text(5, string.format("%d sec Pause", arg1))
end
local i=0
if (now - knife_edge_s) < arg1 then
roll_rate = earth_frame_wings_level(0)
target_pitch = height_PI.update(initial_height)
pitch_rate, yaw_rate = pitch_controller(target_pitch, wp_yaw_deg, PITCH_TCONST:get())
throttle = throttle_controller()
vehicle:set_target_throttle_rate_rpy(throttle, roll_rate, pitch_rate, yaw_rate)
else
running = false
if vehicle:get_mode() == MODE_AUTO then
vehicle:nav_script_time_done(last_id)
else
vehicle:nav_scripting_enable(255)
end
return
end
end
function get_wp_location(i)
local m = mission:get_item(i)
local loc = Location()
loc:lat(m:x())
loc:lng(m:y())
loc:relative_alt(false)
loc:terrain_alt(false)
loc:origin_alt(false)
loc:alt(math.floor(m:z()*100))
return loc
end
function resolve_jump(i)
local m = mission:get_item(i)
while m:command() == DO_JUMP do
i = math.floor(m:param1())
m = mission:get_item(i)
end
return i
end
-- see if an auto mission item needs to be run
function check_auto_mission()
id, cmd, arg1, arg2, arg3, arg4 = vehicle:nav_script_time()
if id then -- we are running a scripting mission
if id ~= last_id then
-- we've started a new command
running = false
last_id = id
repeat_count = 0
initial_yaw_deg = math.deg(ahrs:get_yaw())
initial_height = ahrs:get_position():alt()*0.01
-- work out yaw between previous WP and next WP
local cnum = mission:get_current_nav_index()
-- find previous nav waypoint
local loc_prev = get_wp_location(cnum-1)
local loc_next = get_wp_location(cnum+1)
local i= cnum-1
while get_wp_location(i):lat() == 0 and get_wp_location(i):lng() == 0 do
i = i-1
loc_prev = get_wp_location(i)
end
-- find next nav waypoint
i = cnum+1
while get_wp_location(i):lat() == 0 and get_wp_location(i):lng() == 0 do
i = i+1
loc_next = get_wp_location(resolve_jump(i))
end
wp_yaw_deg = math.deg(loc_prev:get_bearing(loc_next))
end
do_trick(cmd,arg1,arg2)
end
end
local last_trick_action_state = 0
local trick_sel_chan = nil
local last_trick_selection = 0
--[[
get selected trick. Trick numbers are 1 .. TRIK_COUNT. A value of 0 is invalid
--]]
function get_trick_selection()
if trick_sel_chan == nil then
trick_sel_chan = rc:find_channel_for_option(TRIK_SEL_FN:get())
if trick_sel_chan == nil then
return 0
end
end
-- get trick selection based on selection channel input and number of tricks
local i = math.floor(TRIK_COUNT:get() * constrain(0.5*(trick_sel_chan:norm_input_ignore_trim()+1),0,0.999)+1)
if TRICKS[i] == nil then
return 0
end
return i
end
--[[ trick name table
--]]
local name = {}
name[1] = "Roll(s)"
name[2] = "Loop(s)/Turnaround"
name[3] = "Rolling Circle"
name[4] = "Knife-Edge"
--[[
check for running a trick
--]]
function check_trick()
local selection = get_trick_selection()
local action = rc:get_aux_cached(TRIK_ACT_FN:get())
local id =TRICKS[selection].id:get()
if action == 0 and running then
gcs:send_text(0,string.format("Trick aborted"))
running = false
last_trick_selection = 0
-- use invalid mode to disable script control
vehicle:nav_scripting_enable(255)
return 0
end
if selection == 0 then
return 0
end
if action == 1 and selection ~= last_trick_selection then
gcs:send_text(5, string.format("%s selected", name[id]))
last_trick_action_state = action
last_trick_selection = selection
return 0
end
if running then
-- let the task finish
return id
end
if action ~= last_trick_action_state then
last_trick_selection = selection
last_trick_action_state = action
if selection == 0 then
gcs:send_text(0, string.format("No trick selected"))
return 0
end
local id = TRICKS[selection].id:get()
if action == 1 then
gcs:send_text(5, string.format("%s selected ", name[id]))
return 0
end
if action == 2 then
last_trick_selection = selection
local current_mode = vehicle:get_mode()
if not vehicle:nav_scripting_enable(current_mode) then
gcs:send_text(0, string.format("Tricks not available in this mode"))
return 0
end
if id ~= 2 then
gcs:send_text(5, string.format("%s started ", name[id]))
end
wp_yaw_deg = math.deg(ahrs:get_yaw())
initial_height = ahrs:get_position():alt()*0.01
return id
end
end
return 0
end
function do_trick(cmd,arg1,arg2)
if cmd == 1 then
do_axial_roll(arg1, arg2)
elseif cmd == 2 then
do_loop(arg1, arg2)
elseif cmd == 3 then
do_rolling_circle(arg1, arg2)
elseif cmd ==4 then
do_knife_edge(arg1,arg2)
elseif cmd == 5 then
do_pause(arg1,arg2)
end
end
function update()
if vehicle:get_mode() == MODE_AUTO then
check_auto_mission()
else
trick = check_trick()
if trick ~=0 then
arg1 = TRICKS[trick].args[1]:get()
arg2 = TRICKS[trick].args[2]:get()
do_trick(trick,arg1,arg2)
end
end
return update, 10
end
gcs:send_text(5, string.format("Loaded sport_aerobatics.lua"))
last_trick_selection = get_trick_selection() --prevents immediate activation on boot or restart
return update()