PX4 Offboard commads failsafe

Hello PX4 community! :waving_hand:

I’m currently using MAVSDK running on an OBU, which communicates with a Pixhawk running PX4.

I’ve implemented a “move” command that makes the UAV move relatively to its current position (e.g., move 10 m forward).
In that function I:

  • Read the current position using Telemetry::position_velocity_ned.

  • Compute the desired offset.

  • Send Offboard::set_velocity_ned commands so that the UAV moves toward the new position.

  • Once the target is reached, I send a zero-velocity command.

The UAV moves correctly in QGroundControl, but right after it arrives at the destination, I consistently get the following message:

Critical: Failsafe activated: Autopilot disengaged, switching to Position mode
No manual control input

Afterwards I am not able to send any commands anymore.

I’m not sure why PX4 triggers this failsafe.
Has anyone experienced similar behavior?

Any advice or insights would be very much appreciated!
Thank you for your help and time. :folded_hands:

From documentation:

PX4 must receive a stream of MAVLink setpoint messages or the ROS 2 OffboardControlMode at 2 Hz as proof that the external controller is healthy. The stream must be sent for at least a second before PX4 will arm in offboard mode, or switch to offboard mode when flying. If the rate falls below 2Hz while under external control PX4 will switch out of offboard mode after a timeout (COM_OF_LOSS_T), and attempt to land or perform some other failsafe action. The action depends on whether or not RC control is available, and is defined in the parameter COM_OBL_RC_ACT.

If your script not sending commands anymore after reaching desired position this behaviour can be related with this.

Hi Alp_Ucab!

First of all, thank you for your reply!

I tried stopping Offboard mode at the end of my move command. The drone successfully executes the command and switches from Offboard to Hold mode as expected. However, when I try to execute another move command afterward, it immediately triggers a failsafe error and switches back to Position mode, just like before.

On the other hand, if I perform any other command that doesn’t use Offboard mode after the move command, everything works fine.

Do you have any idea why it only works successfully the first time?

I’m not sure about the script structure but I think be sure that your script keep sending commands. Also while running the MAVSDK script printing some mavlink messages from vehicle like simple heartbeat is a good practice to be sure about connection. Maybe first script blocking the port. If you can share more debug messages,warnings etc. it can be helpful for the people who want to help.

Sorry for the lack of context.This is currently the script i am using for the move function: the variable move_as_goto is set to false.

I tried to add the for loop in the stopping command to be sure that i still send commands after arriving destination for at least 10 seconds.

After this change, i am able to perform a single move command, if i try a second one i immediately get the same failsafe error as you can see below:

First image: After first move

Second image: After trying second move

This is the move function:

void CommandHandler::move(Json::Value root)

{

Json::Value coords;

// Calculate latitude and longitude; this is calculated even if using position_ned as the actual command since it is

// useful to publish an approximate target coordinate

std::tuple<double, double> lat_lon =

calcTranslatedCoords(telem->attitude_euler().yaw_deg,

telem->position().latitude_deg,

telem->position().longitude_deg,

root[“x”].asDouble(), root[“y”].asDouble());

coords[“lat”] = std::get<0>(lat_lon);

coords[“lon”] = std::get<1>(lat_lon);

// Calculate altitude

coords[“alt”] = telem->position().absolute_altitude_m + root[“z”].asFloat();

coords[“height”] = telem->position().relative_altitude_m + root[“z”].asFloat();

if (!can_move(“move”, coords))

return;

if (move_as_goto)

{

coords[“yaw”] = root[“yaw”];

coords[“speed”] = root[“speed”];

    // Send command

go_to(coords, true);

}

else

{

    // Calculate new position

std::tuple<double, double, double> new_pos =

calcTranslatedPosition(telem->attitude_euler().yaw_deg,

telem->position_velocity_ned().position.north_m,

telem->position_velocity_ned().position.east_m,

root[“y”].asDouble(), root[“x”].asDouble());

coords[“north”] = std::get<0>(new_pos);

coords[“east”] = std::get<1>(new_pos);

    // Calculate altitude

coords[“down”] = telem->position_velocity_ned().position.down_m - root[“z”].asFloat();

    // If no yaw angle is provided or it is null, face the next waypoint

if (!root.isMember(“yaw”) || root[“yaw”] == Json::nullValue)

coords[“yaw”] = std::get<2>(new_pos);

    // Monitor distance and adjust velocity dynamically

float acceleration = 1.0f; // Aceleração constante (m/s²)

float min_speed = 0.1f; // Velocidade mínima (m/s)

float max_speed = root.isMember(“speed”) ? root[“speed”].asFloat() : this->max_speed();

while (true)

    {

        // Get current position

float current_north = telem->position_velocity_ned().position.north_m;

float current_east = telem->position_velocity_ned().position.east_m;

float current_down = telem->position_velocity_ned().position.down_m;

        // Calculate distance to target

float distance = std::sqrt(

std::pow(coords[“north”].asFloat() - current_north, 2) +

std::pow(coords[“east”].asFloat() - current_east, 2) +

std::pow(coords[“down”].asFloat() - current_down, 2));

        // Stop the drone when close enough

if (distance <= 0.2f)

        {

Json::Value stop_command;

stop_command[“north”] = 0.0f;

stop_command[“east”] = 0.0f;

stop_command[“down”] = 0.0f;

stop_command[“yaw”] = coords[“yaw”];

velocity_ned(stop_command, true);

break;

            // keep sending zero velocity for 10 seconds

for (int i = 0; i < 100; i++) {

velocity_ned(stop_command, true);

usleep(100000); // 100 ms

            }

        }

        // Calculate velocity using cinematics: v = sqrt(2 \* a \* d)

float speed = std::sqrt(2 * acceleration * distance);

        // Clamp speed between min_speed and max_speed

speed = std::max(min_speed, std::min(speed, max_speed));

        // Calculate velocity vector

Json::Value velocity_command;

velocity_command[“north”] = speed * (coords[“north”].asFloat() - current_north) / distance;

velocity_command[“east”] = speed * (coords[“east”].asFloat() - current_east) / distance;

velocity_command[“down”] = speed * (coords[“down”].asFloat() - current_down) / distance;

velocity_command[“yaw”] = coords[“yaw”];

        // Send velocity command

velocity_ned(velocity_command, true);

        // Dynamically adjust delay based on distance (clamped between 10ms and 100ms)

float delay = std::max(0.01f, std::min(0.1f, distance / 10.0f)); // Delay between 10ms and 100ms

usleep(static_cast<useconds_t>(delay * 1e6)); // Convert seconds to microseconds

    }

}

stop_offb(true);

tracker->track_move(root[“y”].asDouble(), root[“x”].asDouble(), root[“z”].asDouble(), coords, move_as_goto);

}

This behaviour related with COM_OBL_RC_ACT very obviously. You should focus on this. Your for loop not working as you expected. Breaking the while(true) loop before for loop calling. So when if (distance <= 0.2f) condition is true (after first move) you’re not sending command anymore.

Also as a suggestion, you can try gemini or google AI studio if not tried yet to solve this type of problems very quickly :slight_smile: I think this is explain why your first move command working but just after arriving the desired location offboard failsafe action triggered.

I actually had that error in my code before, but I had already detected and fixed it — sorry for the confusion. However, moving the break statement after the for loop still results in the same behavior.The drone moves to the place i want, then if i wait 10 seconds it changes to hold. But once i am in hold mode if i try to make a second move i get exactly the same problem. Even if i try a second move during the 10 seconds that i am still in offboard mode (the for loop) the same thing happens.