Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plane: handle guided path requests in GUIDED mode #26328

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3a92238
AP_Navigation: add method update_path
srmainwaring Feb 26, 2024
7b0fc5f
AP_L1_Control: add method update_path
srmainwaring Feb 26, 2024
58cab18
AP_L1_Control: disable loiter radius scaling in guided path navigation
srmainwaring Feb 27, 2024
f8ec3e3
Plane: add guided path navigation
srmainwaring Feb 26, 2024
40b26cc
Plane: update type mask comparisons in set_position_global handler
srmainwaring Mar 12, 2024
51e2704
AP_Navigation: rename position variable to location_on_path
srmainwaring Mar 13, 2024
4035ead
AP_L1_Control: rename position variable to location_on_path
srmainwaring Mar 13, 2024
d4b99e9
Plane: rename position variable to location_on_path
srmainwaring Mar 13, 2024
fa9055e
AP_Navigation: make unit_path_tangent const reference
srmainwaring Mar 13, 2024
172ddf0
AP_L1_Control: make unit_path_tangent const reference
srmainwaring Mar 13, 2024
abc040c
Plane: make unit_path_tangent const reference
srmainwaring Mar 13, 2024
c8a6792
AP_Navigation: rename path guidance function to `follow_path`
srmainwaring Mar 13, 2024
243fa00
AP_L1_Control: rename path guidance function to `follow_path`
srmainwaring Mar 13, 2024
cf74dab
Plane: rename path guidance function to `follow_path`
srmainwaring Mar 13, 2024
1e75916
Plane: make location_on_path const reference in handle_guided_path
srmainwaring Mar 13, 2024
3373fa6
Plane: use brace initialiser for Vector2f
srmainwaring Mar 13, 2024
7a78315
Plane: make local variables that don't change const
srmainwaring Mar 13, 2024
8baaac6
Plane: used standard code style for initialising local floats and bools
srmainwaring Mar 13, 2024
5f84d94
AP_L1_Control: Remove false setter in header
Ryanf55 Oct 28, 2024
1061192
GCS_Mavlink: Use early return to reduce indent
Ryanf55 Oct 28, 2024
bce81f7
AP_L1_Control: Use is_positive
Ryanf55 Oct 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 76 additions & 5 deletions ArduPlane/GCS_Mavlink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1372,15 +1372,28 @@ void GCS_MAVLINK_Plane::handle_set_position_target_global_int(const mavlink_mess

mavlink_set_position_target_global_int_t pos_target;
mavlink_msg_set_position_target_global_int_decode(&msg, &pos_target);

const uint16_t POSITION_TARGET_TYPEMASK_LAST_BYTE = 0xF000;

// Unexpectedly, the mask is expecting "ones" for dimensions that should
// be IGNORNED rather than INCLUDED. See mavlink documentation of the
// SET_POSITION_TARGET_GLOBAL_INT message, type_mask field.
const uint16_t alt_mask = 0b1111111111111011; // (z mask at bit 3)

const uint16_t alt_mask = (
POSITION_TARGET_TYPEMASK_X_IGNORE | POSITION_TARGET_TYPEMASK_Y_IGNORE | POSITION_TARGET_TYPEMASK_Z_IGNORE |
POSITION_TARGET_TYPEMASK_VX_IGNORE | POSITION_TARGET_TYPEMASK_VY_IGNORE | POSITION_TARGET_TYPEMASK_VZ_IGNORE |
POSITION_TARGET_TYPEMASK_AX_IGNORE | POSITION_TARGET_TYPEMASK_AY_IGNORE | POSITION_TARGET_TYPEMASK_AZ_IGNORE |
POSITION_TARGET_TYPEMASK_YAW_IGNORE | POSITION_TARGET_TYPEMASK_YAW_RATE_IGNORE |
POSITION_TARGET_TYPEMASK_LAST_BYTE) ^ 0xFFFF;

// bit mask for path following
const uint16_t path_mask = (
POSITION_TARGET_TYPEMASK_YAW_IGNORE | POSITION_TARGET_TYPEMASK_YAW_RATE_IGNORE |
POSITION_TARGET_TYPEMASK_LAST_BYTE) ^ 0xFFFF;

bool msg_valid = true;
AP_Mission::Mission_Command cmd = {0};
if (pos_target.type_mask & alt_mask)

if (((pos_target.type_mask | POSITION_TARGET_TYPEMASK_LAST_BYTE) ^ 0xFFFF) == alt_mask)
{
cmd.content.location.alt = pos_target.alt * 100;
cmd.content.location.relative_alt = false;
Expand Down Expand Up @@ -1408,7 +1421,65 @@ void GCS_MAVLINK_Plane::handle_set_position_target_global_int(const mavlink_mess
if (msg_valid) {
handle_change_alt_request(cmd);
}
} // end if alt_mask
} // end if alt_mask

// guided path following
if (((pos_target.type_mask | POSITION_TARGET_TYPEMASK_LAST_BYTE) ^ 0xFFFF) == path_mask) {
cmd.content.location.lat = pos_target.lat_int;
cmd.content.location.lng = pos_target.lon_int;

cmd.content.location.alt = pos_target.alt * 100;
cmd.content.location.relative_alt = false;
cmd.content.location.terrain_alt = false;
switch (pos_target.coordinate_frame)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try mavlink_coordinate_frame_to_location_alt_frame

i.e. extract as much useful stuff from our COMMAND_INT / COMMAND_LONG parsing...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase on top of #28479 once that's merged.

{
case MAV_FRAME_GLOBAL_RELATIVE_ALT_INT:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes we move these types of checks right to the top of the function. This just gets it out of the way and allows us to remove some indenting lower down.

cmd.content.location.relative_alt = true;
break;
default:
gcs().send_text(MAV_SEVERITY_WARNING, "Invalid coord frame in SET_POSTION_TARGET_GLOBAL_INT");
msg_valid = false;
break;
}

if (!msg_valid) {
return;
}

const Vector2f vel{pos_target.vx, pos_target.vy};
const Vector2f accel{pos_target.afx, pos_target.afy};
Vector2f unit_vel;

float path_curvature = 0.0;
bool dir_is_ccw = false;

if (!vel.is_zero()) {
unit_vel = vel.normalized();

if (!accel.is_zero()) {
// curvature is determined from the acceleration normal
// to the planar velocity and the equation for uniform
// circular motion: a = v^2 / r.
float accel_proj = accel.dot(unit_vel);
Vector2f accel_lat = accel - unit_vel * accel_proj;
Vector2f accel_lat_unit = accel_lat.normalized();

path_curvature = accel_lat.length() / vel.length_squared();

// % is cross product, direction: cw:= 1, ccw:= -1
float dir = accel_lat_unit % unit_vel;
dir_is_ccw = dir < 0.0;
}
}

// update path guidance
plane.mode_guided.handle_guided_path_request(
cmd.content.location, unit_vel, path_curvature, dir_is_ccw);

// update adjust_altitude_target immediately rather than wait
// for the scheduler.
plane.adjust_altitude_target();
}
}

MAV_RESULT GCS_MAVLINK_Plane::handle_command_do_set_mission_current(const mavlink_command_int_t &packet)
Expand Down
15 changes: 15 additions & 0 deletions ArduPlane/mode.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,17 +314,32 @@ class ModeGuided : public Mode
// handle a guided target request from GCS
bool handle_guided_request(Location target_loc) override;

// handle a guided path following request
bool handle_guided_path_request(const Location& location_on_path, const Vector2f& unit_path_tangent, const float path_curvature, const bool direction_is_ccw);
Ryanf55 marked this conversation as resolved.
Show resolved Hide resolved

void set_radius_and_direction(const float radius, const bool direction_is_ccw);

void update_target_altitude() override;

protected:

enum class SubMode: uint8_t {
WP,
Path
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heading

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also "None"

};

bool _enter() override;
bool _pre_arm_checks(size_t buflen, char *buffer) const override { return true; }

private:

SubMode _guided_mode;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you check that the vehicle always ends up in the WP sub mode when it is first switched to Guided mode? So it probably always does the first time the vehicle is switched to Guided but it may not if the vehicle is in Path sub mode (in Guided), then switched to another mode, then back to Guided.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it's desireable for guided mode to preserve any state when you switch out of it and back into guided?

For example, if you had sent a heading command, switch to RTL for 0.5 seconds, and back to guided, it seems the vehicle should chose the default WP sub mode and drop a point where it enters guided rather than go back to the old stale heading command.

Copy link
Contributor

@timtuxworth timtuxworth Oct 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it's desireable for guided mode to preserve any state when you switch out of it and back into guided?

This is why I added a HEADING_TYPE_DEFAULT mavlink option in #27428 - so you can explicitly switch to GUIDED_HEADING_NONE, because it should definitely not preserve state when switching out of the mode and then back again.


float active_radius_m;

// used in path mode
float _path_curvature;
Vector2f _unit_path_tangent;
};

class ModeCircle: public Mode
Expand Down
42 changes: 41 additions & 1 deletion ArduPlane/mode_guided.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,18 @@ void ModeGuided::update()

void ModeGuided::navigate()
{
plane.update_loiter(active_radius_m);
switch (_guided_mode) {
case SubMode::WP:
plane.update_loiter(active_radius_m);
break;
case SubMode::Path:
plane.nav_controller->follow_path(plane.next_WP_loc,
_unit_path_tangent, _path_curvature, plane.loiter.direction);
break;
default:
gcs().send_text(MAV_SEVERITY_WARNING, "Unknown GUIDED mode");
break;
}
Copy link
Contributor

@timtuxworth timtuxworth Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guided Mode already (logically) has another "sub mode" which is "heading".

ArduPilot/ardupilot_wiki#6096

}

bool ModeGuided::handle_guided_request(Location target_loc)
Expand All @@ -116,6 +127,35 @@ bool ModeGuided::handle_guided_request(Location target_loc)

plane.set_guided_WP(target_loc);

// use waypoint navigation sub-mode
_guided_mode = SubMode::WP;

return true;
}

bool ModeGuided::handle_guided_path_request(const Location& location_on_path, const Vector2f& unit_path_tangent, const float path_curvature, const bool direction_is_ccw)
{
Location location_on_path_abs_alt = location_on_path;

// add home alt if needed
if (location_on_path_abs_alt.relative_alt) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of these bools, just call the altitude getter with the right frame.

location_on_path_abs_alt.alt += plane.home.alt;
location_on_path_abs_alt.relative_alt = 0;
}

// copy the current location into the OldWP slot
plane.prev_WP_loc = plane.current_loc;

// load the next_WP slot
plane.next_WP_loc = location_on_path_abs_alt;

_unit_path_tangent = unit_path_tangent;
_path_curvature = path_curvature;
plane.loiter.direction = direction_is_ccw ? -1 : 1;

// use path navigation sub-mode
_guided_mode = SubMode::Path;

return true;
}

Expand Down
35 changes: 35 additions & 0 deletions libraries/AP_L1_Control/AP_L1_Control.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ float AP_L1_Control::turn_distance(float wp_radius, float turn_angle) const

float AP_L1_Control::loiter_radius(const float radius) const
{
if (_disable_loiter_radius_scaling) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this variable get unset?

return radius;
}

// prevent an insane loiter bank limit
float sanitized_bank_limit = constrain_float(_loiter_bank_limit, 0.0f, 89.0f);
float lateral_accel_sea_level = tanf(radians(sanitized_bank_limit)) * GRAVITY_MSS;
Expand Down Expand Up @@ -545,3 +549,34 @@ void AP_L1_Control::update_level_flight(void)

_data_is_stale = false; // status are correctly updated with current waypoint data
}

// update L1 control for path following
void AP_L1_Control::follow_path(const Location &location_on_path, const Vector2f& unit_path_tangent, float path_curvature, int8_t direction) {
//! @note initial implementation uses existing functions
if (is_positive(path_curvature)) {
// moving along arc of circle - loiter about wp located at
// centre of curvature.
float radius_m = 1.0 / path_curvature;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fabs() ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The curvature is computed as path_curvature = accel_lat.length() / vel.length_squared(); which is always positive.
Should we add a comment that curvature is assumed to be positive in the header?

auto center_wp = location_on_path;
Vector3p tangent_ned(unit_path_tangent.x, unit_path_tangent.y, 0.0);
Vector3p dn_ned(0.0, 0.0, 1.0);
auto ofs_ned = dn_ned.cross(tangent_ned) * radius_m * direction;
center_wp.offset(ofs_ned);

// disable loiter radius scaling while updating loiter
_disable_loiter_radius_scaling = true;
update_loiter(center_wp, radius_m, direction);
_disable_loiter_radius_scaling = false;
} else {
// moving along a line segment - navigate to wp ahead of closest point
// in direction of path tangent
float ofs_m = 100.0;
Vector3p ofs_ned(ofs_m * unit_path_tangent.x,
ofs_m * unit_path_tangent.y, 0.0);
auto prev_wp = location_on_path;
auto next_wp = location_on_path;
next_wp.offset(ofs_ned);

update_waypoint(prev_wp, next_wp);
}
}
4 changes: 4 additions & 0 deletions libraries/AP_L1_Control/AP_L1_Control.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class AP_L1_Control : public AP_Navigation {
void update_loiter(const class Location &center_WP, float radius, int8_t loiter_direction) override;
void update_heading_hold(int32_t navigation_heading_cd) override;
void update_level_flight(void) override;
void follow_path(const Location &location_on_path, const Vector2f& unit_path_tangent, float path_curvature, int8_t direction) override;
bool reached_loiter_target(void) override;

// set the default NAVL1_PERIOD
Expand Down Expand Up @@ -135,4 +136,7 @@ class AP_L1_Control : public AP_Navigation {
bool _reverse = false;
float get_yaw() const;
int32_t get_yaw_sensor() const;

// allow loiter radius scaling to be disabled (in path guidance).
bool _disable_loiter_radius_scaling;
};
10 changes: 10 additions & 0 deletions libraries/AP_Navigation/AP_Navigation.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ class AP_Navigation {
// attitude/steering.
virtual void update_level_flight(void) = 0;

// update the internal state of the navigation controller when
// the vehicle has been commanded with a path following setpoint, which
// includes the closest point on the path, the unit tangent to the path,
// and the curvature. This is the step function for navigation control when
// path following. This function is called at regular intervals
// (typically 10Hz). The main flight code will call an output function
// (such as nav_roll_cd()) after this function to ask for the new required
// navigation attitude/steering.
virtual void follow_path(const Location &location_on_path, const Vector2f& unit_path_tangent, float path_curvature, int8_t direction) {}

// return true if we have reached the target loiter location. This
// may be a fuzzy decision, depending on internal navigation
// parameters. For example the controller may return true only
Expand Down
Loading