Files
ardupilot/libraries/AP_Scripting/examples/copter_pingpong.md
Peter Barker 7ceb7c31c7 global: fix whitespace issues in markdown files
global: fix MD007 unordered list indentation in markdown files

Normalize unordered list indentation to use 2-space multiples:
- Top-level list items start at column 0
- Nested list items use 2 additional spaces per level

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

global: fix MD009 trailing whitespace in markdown files

Remove trailing whitespace from all affected markdown files.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

global: fix MD010 hard tabs in markdown files

Replace hard tab characters with 4 spaces.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

global: fix MD012 multiple consecutive blank lines in markdown

Collapse multiple consecutive blank lines to single blank lines
across all markdown files (excluding vendored code).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

global: fix MD007 list indentation base level in markdown

Shift list indentation left by 2 spaces so top-level list items
start at column 0 instead of column 2.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD022 blank lines around headings in markdown

Ensure headings are surrounded by blank lines as required by
markdownlint MD022 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD032 blank lines around lists in markdown

Ensure lists are surrounded by blank lines as required by
markdownlint MD032 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD031 blank lines around code blocks in markdown

Ensure fenced code blocks are surrounded by blank lines as required
by markdownlint MD031 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD047 files should end with single newline

Ensure all markdown files end with exactly one newline character
as required by markdownlint MD047 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD023 headings must start at beginning of line

Remove leading whitespace from heading lines as required by
markdownlint MD023 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD007 remaining list indentation in markdown

Fix unordered list indentation to use correct spacing as required
by markdownlint MD007 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD030 spaces after list markers in markdown

Reduce multiple spaces after list markers to single space as
required by markdownlint MD030 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD022 blank lines around setext headings

Ensure setext-style headings (underlined with === or ---) are
surrounded by blank lines as required by markdownlint MD022 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD018 missing space after hash in headings

Add space after hash marks in atx-style headings as required by
markdownlint MD018 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD019 multiple spaces after hash in headings

Reduce multiple spaces after hash marks to single space in
atx-style headings as required by markdownlint MD019 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD012 multiple consecutive blank lines in markdown

Remove multiple consecutive blank lines and ensure files end with
exactly one newline as required by markdownlint MD012 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD023 headings with leading whitespace

Remove leading whitespace from setext-style heading text lines
as required by markdownlint MD023 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD022 blank line after heading in markdown

Add missing blank line after heading as required by markdownlint
MD022 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD009 trailing non-breaking space in markdown

Remove trailing non-breaking space (U+00A0) as required by
markdownlint MD009 rule.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Tools/scripts: fix MD012 remaining multiple blank lines in markdown

Remove leading blank lines and whitespace-only lines that create
multiple consecutive blank lines.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:37:38 +11:00

8.1 KiB

Drone Ping-Pong Code

This script makes the drone go forward and backward a defined distance and number of times.

Stages

  • 0: Change to Guided mode
  • 1: Takeoff to the height defined by takeoff_alt
  • 2: Wait until reaching the takeoff altitude
  • 3: Go forward to the defined distance
  • 4: Go back to the initial position
  • 5: Change to Land mode

Variables:

  • takeoff_alt: Takeoff height (m)
  • copter_guided_mode_num: Guided mode number
  • copter_land_mode_num: Land mode number
  • stage: current stage
  • count: Number of times the drone has gone forward
  • max_count: Maximum number of times the drone should go forward
  • ping_pong_distance: Distance up to which the drone should go forward (m)
  • vel: Drone velocity (m/s)

Understand the code:

First, there is a comment explaining what the code is about and how it works. Then, the local variables that will be used are declared. The copter_guided_mode_num and copter_land_mode_num are standard numbers, defined by ArduPilot, for the Guided and Land flight modes, respectively. The other variables are user-defined settings according to the desired behavior, as indicated in the comments in front of each one.

-- This script makes the drone go forward and backward at a defined distance and number of times.
-- The stages are:
-- 0) Change to Guided mode
-- 1) Takeoff to the height defined by takeoff_alt
-- 2) Wait until reaching the takeoff altitude
-- 3) Go forward to the defined distance
-- 4) Go back to the initial position
-- 5) Change to Land mode


local takeoff_alt = 3         -- Takeoff height
local copter_guided_mode_num = 4
local copter_land_mode_num = 9
local stage = 0
local count = 0               -- Number of times the drone has gone forward
local max_count = 2           -- Maximum number of times the drone should go forward
local ping_pong_distance = 10 -- Distance up to which the drone should go forward (m)
local vel = 1

Next, there's the main function of the code, the update() function, which will be called once the code is started (since it wasn't indicated a waiting time at the last line of the code) and its return indicates that this same fucntion will be called again 100ms after finishing its executuion.

function update()
    -- [Code]
    return update(), 100
end

return update()

In the presented if, the code is waiting for the drone to be armed to change to the next stage. The is_armed() function from the arming library is used to check if the drone is armed or not. The send_text() function from the gcs library is used to send a message ("Arming") with severity 6 (information) to the GCS (Ground Control Station). Thus, while the drone is not armed, it remains in stage 0 of the code.

if not arming:is_armed() then
    stage = 0
    gcs:send_text(6, "Arming")

If the drone is armed, it moves to the else section, which presents a behavior for each stage. In stage 0, the drone flight mode is changed to GUIDED MODE. To do this, it's used the function set_mode() of vehicle library, which receives the desired flight mode number(copter_guided_mode_num, defined in the local variables). When it verifies the drone has switched to the desired flight mode, it moves to the next stage.

else
    if stage == 0 then
        if vehicle:set_mode(copter_guided_mode_num) then
            stage = stage + 1
        end

in stage 1, it's used the start_takeoff() function from the vehicle library for the drone to take off to a height defined by the variable takeoff_alt.

elseif stage == 1 then
    gcs:send_text(6, "Taking off")
    if vehicle:start_takeoff(takeoff_alt) then
        stage = stage + 1
    end

In stage 2, it's used the ahrs:get_home() function to get the drone's takeoff location, and the ahrs:get_position() function to get the drone's current position. In line 4, it checks that the obtained values are not null. Then, the home:get_distance_NED() function stores in vec_from_home a 3D vector, starting from curr_loc and ending at home. In line 7, the code sends to the GCS the value contained in the z-coordinate of vec_from_home, i.e., the current altitude of the drone (multiplied by -1 since the vector points towards home, which is at a lower altitude than the current position).

When the difference between takeoff_alt and vec_from_home:z() is less than 1, indicating that the drone has reached the takeoff altitude, it moves to the next stage (math.abs() is used to get the absolute value, and we performed an addition instead of subtraction because the z component is negative).

elseif stage == 2 then
    local home = ahrs:get_home()
    local curr_loc = ahrs:get_position()
    if home and curr_loc then
        local vec_from_home = home:get_distance_NED(curr_loc)
        gcs:send_text(6, "Altitude above home: " .. tostring(math.floor(-vec_from_home:z())))
        if math.abs(takeoff_alt + vec_from_home:z()) < 1 then
            stage = stage + 1
        end
    end

In stage3, first, the drone checks if it has already performed all the requested loops specified in max_count. If so, it switches to stage5. If not, it executes the commands of stage3.

First, it creates a 3D vector target_vel to store the desired velocity. Then, it sets the value of each of the components of this created vector (0 for y and z, and vel for x).

Then, it uses the vehicle:set_target_velocity_NED(target_vel) function to set the drone's velocity as that of target_vel. If it encounters any issues, it sends a warning message.

elseif (stage == 3) then -- Stage 3: Moving Forward
    -- If the maximum number of times is exceeded, move to stage 5
    if (count >= max_count) then
        stage = stage + 2
    end

    -- Calculate velocity vector
    local target_vel = Vector3f()
    target_vel:x(vel)
    target_vel:y(0)
    target_vel:z(0)

    -- Send velocity request
    if not (vehicle:set_target_velocity_NED(target_vel)) then
        gcs:send_text(6, "Failed to execute velocity command")
    end

After that, it uses the same strategy as shown before to calculate the distance vector from the takeoff location to verify the distance x traveled. When the difference between the distance x that the drone traveled and the ping_pong_distance is less than 1, it increments the count and moves to the next stage.


    -- checking if reached stop point
    local home = ahrs:get_home()
    local curr_loc = ahrs:get_position()
    if home and curr_loc then
        local vec_from_home = home:get_distance_NED(curr_loc)
        gcs:send_text(6, "Distance from home: " .. tostring(math.floor(vec_from_home:x())))
        if(math.abs(ping_pong_distance - vec_from_home:x()) < 1) then
            count = count + 1
            stage = stage + 1
        end
    end
end

In Stage4, the velocity in x is multiplied by -1 for the drone to fly in the opposite direction, returning to the takeoff position. When the difference between the drone's position x and the takeoff position's x is less than 1, it goes back to Stage3 to start the forward movement again (or not, if the count has reached max_count).

elseif (stage == 4) then -- Stage 4: Moving Back
    -- calculate velocity vector
    local target_vel = Vector3f()
    target_vel:x(-vel)
    target_vel:y(0)
    target_vel:z(0)

    -- send velocity request
    if not (vehicle:set_target_velocity_NED(target_vel)) then
        gcs:send_text(6, "Failed to execute velocity command")
    end

    -- checking if reached stop point
    local home = ahrs:get_home()
    local curr_loc = ahrs:get_position()
    if home and curr_loc then
        local vec_from_home = home:get_distance_NED(curr_loc)
        gcs:send_text(6, "Distance from home: " .. tostring(math.floor(vec_from_home:x())))
        if(math.abs(vec_from_home:x()) < 1) then
            stage = stage - 1
        end
    end
end

In stage5, the drone simply changes to the Land flight mode and lands, indicating the completion of the code through a message.

elseif (stage == 5) then -- Stage 5: Change to LAND mode
    vehicle:set_mode(copter_rtl_mode_num)
    stage = stage + 1
    gcs:send_text(6, "Finished pingpong, switching to LAND")
end