mirror of
https://github.com/ArduPilot/ardupilot
synced 2025-01-03 14:38:30 -04:00
bc3ff20871
The orient parameter will help us construct a direction of approach vector in vehicle body frame. This vector would help us rotate the target vector from sensor frame to body frame and determine the horizontal position error of vehicle for more orientations than just PITCH_270
776 lines
32 KiB
C++
776 lines
32 KiB
C++
#include <AP_HAL/AP_HAL.h>
|
|
#include <AP_Scheduler/AP_Scheduler.h>
|
|
#include <AP_AHRS/AP_AHRS.h>
|
|
#include "AC_PrecLand.h"
|
|
#include "AC_PrecLand_Backend.h"
|
|
#include "AC_PrecLand_Companion.h"
|
|
#include "AC_PrecLand_IRLock.h"
|
|
#include "AC_PrecLand_SITL_Gazebo.h"
|
|
#include "AC_PrecLand_SITL.h"
|
|
#include <AP_Logger/AP_Logger.h>
|
|
#include <GCS_MAVLink/GCS.h>
|
|
|
|
extern const AP_HAL::HAL& hal;
|
|
|
|
#if APM_BUILD_TYPE(APM_BUILD_Rover)
|
|
# define AC_PRECLAND_ORIENT_DEFAULT Rotation::ROTATION_NONE
|
|
#else
|
|
# define AC_PRECLAND_ORIENT_DEFAULT Rotation::ROTATION_PITCH_270
|
|
#endif
|
|
|
|
static const uint32_t EKF_INIT_TIME_MS = 2000; // EKF initialisation requires this many milliseconds of good sensor data
|
|
static const uint32_t EKF_INIT_SENSOR_MIN_UPDATE_MS = 500; // Sensor must update within this many ms during EKF init, else init will fail
|
|
static const uint32_t LANDING_TARGET_TIMEOUT_MS = 2000; // Sensor must update within this many ms, else prec landing will be switched off
|
|
static const uint32_t LANDING_TARGET_LOST_TIMEOUT_MS = 180000; // Target will be considered as "lost" if the last known location of the target is more than this many ms ago
|
|
static const float LANDING_TARGET_LOST_DIST_THRESH_M = 30; // If the last known location of the landing target is beyond this many meters, then we will consider it lost
|
|
|
|
const AP_Param::GroupInfo AC_PrecLand::var_info[] = {
|
|
// @Param: ENABLED
|
|
// @DisplayName: Precision Land enabled/disabled
|
|
// @Description: Precision Land enabled/disabled
|
|
// @Values: 0:Disabled, 1:Enabled
|
|
// @User: Advanced
|
|
AP_GROUPINFO_FLAGS("ENABLED", 0, AC_PrecLand, _enabled, 0, AP_PARAM_FLAG_ENABLE),
|
|
|
|
// @Param: TYPE
|
|
// @DisplayName: Precision Land Type
|
|
// @Description: Precision Land Type
|
|
// @Values: 0:None, 1:CompanionComputer, 2:IRLock, 3:SITL_Gazebo, 4:SITL
|
|
// @User: Advanced
|
|
AP_GROUPINFO("TYPE", 1, AC_PrecLand, _type, 0),
|
|
|
|
// @Param: YAW_ALIGN
|
|
// @DisplayName: Sensor yaw alignment
|
|
// @Description: Yaw angle from body x-axis to sensor x-axis.
|
|
// @Range: 0 36000
|
|
// @Increment: 10
|
|
// @User: Advanced
|
|
// @Units: cdeg
|
|
AP_GROUPINFO("YAW_ALIGN", 2, AC_PrecLand, _yaw_align, 0),
|
|
|
|
// @Param: LAND_OFS_X
|
|
// @DisplayName: Land offset forward
|
|
// @Description: Desired landing position of the camera forward of the target in vehicle body frame
|
|
// @Range: -20 20
|
|
// @Increment: 1
|
|
// @User: Advanced
|
|
// @Units: cm
|
|
AP_GROUPINFO("LAND_OFS_X", 3, AC_PrecLand, _land_ofs_cm_x, 0),
|
|
|
|
// @Param: LAND_OFS_Y
|
|
// @DisplayName: Land offset right
|
|
// @Description: desired landing position of the camera right of the target in vehicle body frame
|
|
// @Range: -20 20
|
|
// @Increment: 1
|
|
// @User: Advanced
|
|
// @Units: cm
|
|
AP_GROUPINFO("LAND_OFS_Y", 4, AC_PrecLand, _land_ofs_cm_y, 0),
|
|
|
|
// @Param: EST_TYPE
|
|
// @DisplayName: Precision Land Estimator Type
|
|
// @Description: Specifies the estimation method to be used
|
|
// @Values: 0:RawSensor, 1:KalmanFilter
|
|
// @User: Advanced
|
|
AP_GROUPINFO("EST_TYPE", 5, AC_PrecLand, _estimator_type, 1),
|
|
|
|
// @Param: ACC_P_NSE
|
|
// @DisplayName: Kalman Filter Accelerometer Noise
|
|
// @Description: Kalman Filter Accelerometer Noise, higher values weight the input from the camera more, accels less
|
|
// @Range: 0.5 5
|
|
// @User: Advanced
|
|
AP_GROUPINFO("ACC_P_NSE", 6, AC_PrecLand, _accel_noise, 2.5f),
|
|
|
|
// @Param: CAM_POS_X
|
|
// @DisplayName: Camera X position offset
|
|
// @Description: X position of the camera in body frame. Positive X is forward of the origin.
|
|
// @Units: m
|
|
// @Range: -5 5
|
|
// @Increment: 0.01
|
|
// @User: Advanced
|
|
|
|
// @Param: CAM_POS_Y
|
|
// @DisplayName: Camera Y position offset
|
|
// @Description: Y position of the camera in body frame. Positive Y is to the right of the origin.
|
|
// @Units: m
|
|
// @Range: -5 5
|
|
// @Increment: 0.01
|
|
// @User: Advanced
|
|
|
|
// @Param: CAM_POS_Z
|
|
// @DisplayName: Camera Z position offset
|
|
// @Description: Z position of the camera in body frame. Positive Z is down from the origin.
|
|
// @Units: m
|
|
// @Range: -5 5
|
|
// @Increment: 0.01
|
|
// @User: Advanced
|
|
AP_GROUPINFO("CAM_POS", 7, AC_PrecLand, _cam_offset, 0.0f),
|
|
|
|
// @Param: BUS
|
|
// @DisplayName: Sensor Bus
|
|
// @Description: Precland sensor bus for I2C sensors.
|
|
// @Values: -1:DefaultBus,0:InternalI2C,1:ExternalI2C
|
|
// @User: Advanced
|
|
AP_GROUPINFO("BUS", 8, AC_PrecLand, _bus, -1),
|
|
|
|
// @Param: LAG
|
|
// @DisplayName: Precision Landing sensor lag
|
|
// @Description: Precision Landing sensor lag, to cope with variable landing_target latency
|
|
// @Range: 0.02 0.250
|
|
// @Increment: 1
|
|
// @Units: s
|
|
// @User: Advanced
|
|
// @RebootRequired: True
|
|
AP_GROUPINFO("LAG", 9, AC_PrecLand, _lag, 0.02f), // 20ms is the old default buffer size (8 frames @ 400hz/2.5ms)
|
|
|
|
// @Param: XY_DIST_MAX
|
|
// @DisplayName: Precision Landing maximum distance to target before descending
|
|
// @Description: The vehicle will not start descending if the landing target is detected and it is further than this many meters away. Set 0 to always descend.
|
|
// @Range: 0 10
|
|
// @Units: m
|
|
// @User: Advanced
|
|
AP_GROUPINFO("XY_DIST_MAX", 10, AC_PrecLand, _xy_max_dist_desc, 2.5f),
|
|
// @Param: STRICT
|
|
// @DisplayName: PrecLand strictness
|
|
// @Description: How strictly should the vehicle land on the target if target is lost
|
|
// @Values: 0: Land Vertically (Not strict), 1: Retry Landing(Normal Strictness), 2: Do not land (just Hover) (Very Strict)
|
|
AP_GROUPINFO("STRICT", 11, AC_PrecLand, _strict, 1),
|
|
|
|
// @Param: RET_MAX
|
|
// @DisplayName: PrecLand Maximum number of retires for a failed landing
|
|
// @Description: PrecLand Maximum number of retires for a failed landing. Set to zero to disable landing retry.
|
|
// @Range: 0 10
|
|
// @Increment: 1
|
|
AP_GROUPINFO("RET_MAX", 12, AC_PrecLand, _retry_max, 4),
|
|
|
|
// @Param: TIMEOUT
|
|
// @DisplayName: PrecLand retry timeout
|
|
// @Description: Time for which vehicle continues descend even if target is lost. After this time period, vehicle will attemp a landing retry depending on PLND_STRICT parameter.
|
|
// @Range: 0 20
|
|
// @Units: s
|
|
AP_GROUPINFO("TIMEOUT", 13, AC_PrecLand, _retry_timeout_sec, 4),
|
|
|
|
// @Param: RET_BEHAVE
|
|
// @DisplayName: PrecLand retry behaviour
|
|
// @Description: Prec Land will do the action selected by this parameter if a retry to a landing is needed
|
|
// @Values: 0: Go to the last location where landing target was detected, 1: Go towards the approximate location of the detected landing target
|
|
AP_GROUPINFO("RET_BEHAVE", 14, AC_PrecLand, _retry_behave, 0),
|
|
|
|
// @Param: ALT_MIN
|
|
// @DisplayName: PrecLand minimum alt for retry
|
|
// @Description: Vehicle will continue landing vertically even if target is lost below this height. This needs a rangefinder to work. Set to zero to disable this.
|
|
// @Range: 0 5
|
|
// @Units: m
|
|
AP_GROUPINFO("ALT_MIN", 15, AC_PrecLand, _sensor_min_alt, 0.75),
|
|
|
|
// @Param: ALT_MAX
|
|
// @DisplayName: PrecLand maximum alt for retry
|
|
// @Description: Vehicle will continue landing vertically until this height if target is not found. Below this height if landing target is not found, landing retry/failsafe might be attempted. This needs a rangefinder to work. Set to zero to disable this.
|
|
// @Range: 0 50
|
|
// @Units: m
|
|
AP_GROUPINFO("ALT_MAX", 16, AC_PrecLand, _sensor_max_alt, 8),
|
|
|
|
// @Param: OPTIONS
|
|
// @DisplayName: Precision Landing Extra Options
|
|
// @Description: Precision Landing Extra Options
|
|
// @Bitmask: 0: Moving Landing Target
|
|
// @User: Advanced
|
|
AP_GROUPINFO("OPTIONS", 17, AC_PrecLand, _options, 0),
|
|
|
|
// @Param: ORIENT
|
|
// @DisplayName: Camera Orientation
|
|
// @Description: Orientation of camera/sensor on body
|
|
// @Values: 0:Forward, 4:Back, 25:Down
|
|
// @User: Advanced
|
|
// @RebootRequired: True
|
|
AP_GROUPINFO_FRAME("ORIENT", 18, AC_PrecLand, _orient, AC_PRECLAND_ORIENT_DEFAULT, AP_PARAM_FRAME_ROVER),
|
|
|
|
AP_GROUPEND
|
|
};
|
|
|
|
// Default constructor.
|
|
AC_PrecLand::AC_PrecLand()
|
|
{
|
|
if (_singleton != nullptr) {
|
|
AP_HAL::panic("AC_PrecLand must be singleton");
|
|
}
|
|
_singleton = this;
|
|
|
|
// set parameters to defaults
|
|
AP_Param::setup_object_defaults(this, var_info);
|
|
}
|
|
|
|
// perform any required initialisation of landing controllers
|
|
// update_rate_hz should be the rate at which the update method will be called in hz
|
|
void AC_PrecLand::init(uint16_t update_rate_hz)
|
|
{
|
|
// exit immediately if init has already been run
|
|
if (_backend != nullptr) {
|
|
return;
|
|
}
|
|
|
|
// init as target TARGET_NEVER_SEEN, we will update this later
|
|
_current_target_state = TargetState::TARGET_NEVER_SEEN;
|
|
|
|
// default health to false
|
|
_backend = nullptr;
|
|
_backend_state.healthy = false;
|
|
|
|
// create inertial history buffer
|
|
// constrain lag parameter to be within bounds
|
|
_lag.set(constrain_float(_lag, 0.02f, 0.25f));
|
|
|
|
// calculate inertial buffer size from lag and minimum of main loop rate and update_rate_hz argument
|
|
const uint16_t inertial_buffer_size = MAX((uint16_t)roundf(_lag * MIN(update_rate_hz, AP::scheduler().get_loop_rate_hz())), 1);
|
|
|
|
// instantiate ring buffer to hold inertial history, return on failure so no backends are created
|
|
_inertial_history = new ObjectArray<inertial_data_frame_s>(inertial_buffer_size);
|
|
if (_inertial_history == nullptr) {
|
|
return;
|
|
}
|
|
|
|
// instantiate backend based on type parameter
|
|
switch ((Type)(_type.get())) {
|
|
// no type defined
|
|
case Type::NONE:
|
|
default:
|
|
return;
|
|
// companion computer
|
|
case Type::COMPANION:
|
|
_backend = new AC_PrecLand_Companion(*this, _backend_state);
|
|
break;
|
|
// IR Lock
|
|
case Type::IRLOCK:
|
|
_backend = new AC_PrecLand_IRLock(*this, _backend_state);
|
|
break;
|
|
#if CONFIG_HAL_BOARD == HAL_BOARD_SITL
|
|
case Type::SITL_GAZEBO:
|
|
_backend = new AC_PrecLand_SITL_Gazebo(*this, _backend_state);
|
|
break;
|
|
case Type::SITL:
|
|
_backend = new AC_PrecLand_SITL(*this, _backend_state);
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
// init backend
|
|
if (_backend != nullptr) {
|
|
_backend->init();
|
|
}
|
|
|
|
_approach_vector_body.x = 1;
|
|
_approach_vector_body.rotate(_orient);
|
|
}
|
|
|
|
// update - give chance to driver to get updates from sensor
|
|
void AC_PrecLand::update(float rangefinder_alt_cm, bool rangefinder_alt_valid)
|
|
{
|
|
// exit immediately if not enabled
|
|
if (_backend == nullptr || _inertial_history == nullptr) {
|
|
return;
|
|
}
|
|
|
|
// append current velocity and attitude correction into history buffer
|
|
struct inertial_data_frame_s inertial_data_newest;
|
|
const auto &_ahrs = AP::ahrs();
|
|
_ahrs.getCorrectedDeltaVelocityNED(inertial_data_newest.correctedVehicleDeltaVelocityNED, inertial_data_newest.dt);
|
|
inertial_data_newest.Tbn = _ahrs.get_rotation_body_to_ned();
|
|
Vector3f curr_vel;
|
|
nav_filter_status status;
|
|
if (!_ahrs.get_velocity_NED(curr_vel) || !_ahrs.get_filter_status(status)) {
|
|
inertial_data_newest.inertialNavVelocityValid = false;
|
|
} else {
|
|
inertial_data_newest.inertialNavVelocityValid = status.flags.horiz_vel;
|
|
}
|
|
curr_vel.z = -curr_vel.z; // NED to NEU
|
|
inertial_data_newest.inertialNavVelocity = curr_vel;
|
|
|
|
inertial_data_newest.time_usec = AP_HAL::micros64();
|
|
_inertial_history->push_force(inertial_data_newest);
|
|
|
|
const float rangefinder_alt_m = rangefinder_alt_cm*0.01f; //cm to meter
|
|
|
|
// update estimator of target position
|
|
if (_backend != nullptr && _enabled) {
|
|
_backend->update();
|
|
run_estimator(rangefinder_alt_m, rangefinder_alt_valid);
|
|
}
|
|
|
|
// check the status of the landing target location
|
|
check_target_status(rangefinder_alt_m, rangefinder_alt_valid);
|
|
|
|
const uint32_t now = AP_HAL::millis();
|
|
if (now - last_log_ms > 40) { // 25Hz
|
|
last_log_ms = now;
|
|
Write_Precland();
|
|
}
|
|
}
|
|
|
|
// check the status of the target
|
|
void AC_PrecLand::check_target_status(float rangefinder_alt_m, bool rangefinder_alt_valid)
|
|
{
|
|
if (target_acquired()) {
|
|
// target in sight
|
|
_current_target_state = TargetState::TARGET_FOUND;
|
|
// early return because we already know the status
|
|
return;
|
|
}
|
|
|
|
// target not in sight
|
|
if (_current_target_state == TargetState::TARGET_FOUND ||
|
|
_current_target_state == TargetState::TARGET_RECENTLY_LOST) {
|
|
// we had target in sight, but not any more, i.e we have lost the target
|
|
_current_target_state = TargetState::TARGET_RECENTLY_LOST;
|
|
} else {
|
|
// we never had the target in sight
|
|
_current_target_state = TargetState::TARGET_NEVER_SEEN;
|
|
}
|
|
|
|
// We definitely do not have the target in sight
|
|
// check if the precision landing sensor is supposed to be in range
|
|
// this needs a valid rangefinder to work
|
|
if (!check_if_sensor_in_range(rangefinder_alt_m, rangefinder_alt_valid)) {
|
|
// Target is not in range (vehicle is either too high or too low). Vehicle will not be attempting any sort of landing retries during this period
|
|
_current_target_state = TargetState::TARGET_OUT_OF_RANGE;
|
|
return;
|
|
}
|
|
|
|
if (_current_target_state == TargetState::TARGET_RECENTLY_LOST) {
|
|
// check if it's nearby/found recently, else the status will be demoted to "TARGET_LOST"
|
|
Vector2f curr_pos;
|
|
if (AP::ahrs().get_relative_position_NE_origin(curr_pos)) {
|
|
const float dist_to_last_target_loc_xy = (curr_pos - Vector2f{_last_target_pos_rel_origin_NED.x, _last_target_pos_rel_origin_NED.y}).length();
|
|
const float dist_to_last_loc_xy = (curr_pos - Vector2f{_last_vehicle_pos_NED.x, _last_vehicle_pos_NED.y}).length();
|
|
if ((AP_HAL::millis() - _last_valid_target_ms) > LANDING_TARGET_LOST_TIMEOUT_MS) {
|
|
// the target has not been seen for a long time
|
|
// might as well consider it as "never seen"
|
|
_current_target_state = TargetState::TARGET_NEVER_SEEN;
|
|
return;
|
|
}
|
|
|
|
if ((dist_to_last_target_loc_xy > LANDING_TARGET_LOST_DIST_THRESH_M) || (dist_to_last_loc_xy > LANDING_TARGET_LOST_DIST_THRESH_M)) {
|
|
// the last known location of target is too far away
|
|
_current_target_state = TargetState::TARGET_NEVER_SEEN;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if the landing target is supposed to be in sight based on the height of the vehicle from the ground
|
|
// This needs a valid rangefinder to work, if the min/max parameters are non zero
|
|
bool AC_PrecLand::check_if_sensor_in_range(float rangefinder_alt_m, bool rangefinder_alt_valid) const
|
|
{
|
|
if (is_zero(_sensor_max_alt) && is_zero(_sensor_min_alt)) {
|
|
// no sensor limits have been specified, assume sensor is always in range
|
|
return true;
|
|
}
|
|
|
|
if (!rangefinder_alt_valid) {
|
|
// rangefinder isn't healthy. We might be at a very high altitude
|
|
return false;
|
|
}
|
|
|
|
if (rangefinder_alt_m > _sensor_max_alt && !is_zero(_sensor_max_alt)) {
|
|
// this prevents triggering a retry when we are too far away from the target
|
|
return false;
|
|
}
|
|
|
|
if (rangefinder_alt_m < _sensor_min_alt && !is_zero(_sensor_min_alt)) {
|
|
// this prevents triggering a retry when we are very close to the target
|
|
return false;
|
|
}
|
|
|
|
// target should be in range
|
|
return true;
|
|
}
|
|
|
|
bool AC_PrecLand::target_acquired()
|
|
{
|
|
if ((AP_HAL::millis()-_last_update_ms) > LANDING_TARGET_TIMEOUT_MS) {
|
|
if (_target_acquired) {
|
|
// just lost the landing target, inform the user. This message will only be sent once everytime target is lost
|
|
gcs().send_text(MAV_SEVERITY_CRITICAL, "PrecLand: Target Lost");
|
|
}
|
|
// not had a sensor update since a long time
|
|
// probably lost the target
|
|
_estimator_initialized = false;
|
|
_target_acquired = false;
|
|
}
|
|
return _target_acquired;
|
|
}
|
|
|
|
bool AC_PrecLand::get_target_position_cm(Vector2f& ret)
|
|
{
|
|
if (!target_acquired()) {
|
|
return false;
|
|
}
|
|
Vector2f curr_pos;
|
|
if (!AP::ahrs().get_relative_position_NE_origin(curr_pos)) {
|
|
return false;
|
|
}
|
|
ret.x = (_target_pos_rel_out_NE.x + curr_pos.x) * 100.0f; // m to cm
|
|
ret.y = (_target_pos_rel_out_NE.y + curr_pos.y) * 100.0f; // m to cm
|
|
return true;
|
|
}
|
|
|
|
void AC_PrecLand::get_target_position_measurement_cm(Vector3f& ret)
|
|
{
|
|
ret = _target_pos_rel_meas_NED*100.0f;
|
|
return;
|
|
}
|
|
|
|
bool AC_PrecLand::get_target_position_relative_cm(Vector2f& ret)
|
|
{
|
|
if (!target_acquired()) {
|
|
return false;
|
|
}
|
|
ret = _target_pos_rel_out_NE*100.0f;
|
|
return true;
|
|
}
|
|
|
|
bool AC_PrecLand::get_target_velocity_relative_cms(Vector2f& ret)
|
|
{
|
|
if (!target_acquired()) {
|
|
return false;
|
|
}
|
|
ret = _target_vel_rel_out_NE*100.0f;
|
|
return true;
|
|
}
|
|
|
|
// get the absolute velocity of the vehicle
|
|
void AC_PrecLand::get_target_velocity_cms(const Vector2f& vehicle_velocity_cms, Vector2f& target_vel_cms)
|
|
{
|
|
if (!(_options & PLND_OPTION_MOVING_TARGET)) {
|
|
// the target should not be moving
|
|
target_vel_cms.zero();
|
|
return;
|
|
}
|
|
if ((EstimatorType)_estimator_type.get() == EstimatorType::RAW_SENSOR) {
|
|
// We do not predict the velocity of the target in this case
|
|
// assume velocity to be zero
|
|
target_vel_cms.zero();
|
|
return;
|
|
}
|
|
Vector2f target_vel_rel_cms;
|
|
if (!get_target_velocity_relative_cms(target_vel_rel_cms)) {
|
|
// Don't know where the target is
|
|
// assume velocity to be zero
|
|
target_vel_cms.zero();
|
|
return;
|
|
}
|
|
// return the absolute velocity
|
|
target_vel_cms = target_vel_rel_cms + vehicle_velocity_cms;
|
|
}
|
|
|
|
// handle_msg - Process a LANDING_TARGET mavlink message
|
|
void AC_PrecLand::handle_msg(const mavlink_landing_target_t &packet, uint32_t timestamp_ms)
|
|
{
|
|
// run backend update
|
|
if (_backend != nullptr) {
|
|
_backend->handle_msg(packet, timestamp_ms);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Private methods
|
|
//
|
|
|
|
void AC_PrecLand::run_estimator(float rangefinder_alt_m, bool rangefinder_alt_valid)
|
|
{
|
|
const struct inertial_data_frame_s *inertial_data_delayed = (*_inertial_history)[0];
|
|
|
|
switch ((EstimatorType)_estimator_type.get()) {
|
|
case EstimatorType::RAW_SENSOR: {
|
|
// Return if there's any invalid velocity data
|
|
for (uint8_t i=0; i<_inertial_history->available(); i++) {
|
|
const struct inertial_data_frame_s *inertial_data = (*_inertial_history)[i];
|
|
if (!inertial_data->inertialNavVelocityValid) {
|
|
_target_acquired = false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Predict
|
|
if (target_acquired()) {
|
|
_target_pos_rel_est_NE.x -= inertial_data_delayed->inertialNavVelocity.x * inertial_data_delayed->dt;
|
|
_target_pos_rel_est_NE.y -= inertial_data_delayed->inertialNavVelocity.y * inertial_data_delayed->dt;
|
|
_target_vel_rel_est_NE.x = -inertial_data_delayed->inertialNavVelocity.x;
|
|
_target_vel_rel_est_NE.y = -inertial_data_delayed->inertialNavVelocity.y;
|
|
}
|
|
|
|
// Update if a new Line-Of-Sight measurement is available
|
|
if (construct_pos_meas_using_rangefinder(rangefinder_alt_m, rangefinder_alt_valid)) {
|
|
if (!_estimator_initialized) {
|
|
gcs().send_text(MAV_SEVERITY_INFO, "PrecLand: Target Found");
|
|
_estimator_initialized = true;
|
|
}
|
|
_target_pos_rel_est_NE.x = _target_pos_rel_meas_NED.x;
|
|
_target_pos_rel_est_NE.y = _target_pos_rel_meas_NED.y;
|
|
_target_vel_rel_est_NE.x = -inertial_data_delayed->inertialNavVelocity.x;
|
|
_target_vel_rel_est_NE.y = -inertial_data_delayed->inertialNavVelocity.y;
|
|
|
|
_last_update_ms = AP_HAL::millis();
|
|
_target_acquired = true;
|
|
}
|
|
|
|
// Output prediction
|
|
if (target_acquired()) {
|
|
run_output_prediction();
|
|
}
|
|
break;
|
|
}
|
|
case EstimatorType::KALMAN_FILTER: {
|
|
// Predict
|
|
if (target_acquired() || _estimator_initialized) {
|
|
const float& dt = inertial_data_delayed->dt;
|
|
const Vector3f& vehicleDelVel = inertial_data_delayed->correctedVehicleDeltaVelocityNED;
|
|
|
|
_ekf_x.predict(dt, -vehicleDelVel.x, _accel_noise*dt);
|
|
_ekf_y.predict(dt, -vehicleDelVel.y, _accel_noise*dt);
|
|
}
|
|
|
|
// Update if a new Line-Of-Sight measurement is available
|
|
if (construct_pos_meas_using_rangefinder(rangefinder_alt_m, rangefinder_alt_valid)) {
|
|
float xy_pos_var = sq(_target_pos_rel_meas_NED.z*(0.01f + 0.01f*AP::ahrs().get_gyro().length()) + 0.02f);
|
|
if (!_estimator_initialized) {
|
|
// Inform the user landing target has been found
|
|
gcs().send_text(MAV_SEVERITY_INFO, "PrecLand: Target Found");
|
|
// start init of EKF. We will let the filter consume the data for a while before it available for consumption
|
|
// reset filter state
|
|
if (inertial_data_delayed->inertialNavVelocityValid) {
|
|
_ekf_x.init(_target_pos_rel_meas_NED.x, xy_pos_var, -inertial_data_delayed->inertialNavVelocity.x, sq(2.0f));
|
|
_ekf_y.init(_target_pos_rel_meas_NED.y, xy_pos_var, -inertial_data_delayed->inertialNavVelocity.y, sq(2.0f));
|
|
} else {
|
|
_ekf_x.init(_target_pos_rel_meas_NED.x, xy_pos_var, 0.0f, sq(10.0f));
|
|
_ekf_y.init(_target_pos_rel_meas_NED.y, xy_pos_var, 0.0f, sq(10.0f));
|
|
}
|
|
_last_update_ms = AP_HAL::millis();
|
|
_estimator_init_ms = AP_HAL::millis();
|
|
// we have initialized the estimator but will not use the values for sometime so that EKF settles down
|
|
_estimator_initialized = true;
|
|
} else {
|
|
float NIS_x = _ekf_x.getPosNIS(_target_pos_rel_meas_NED.x, xy_pos_var);
|
|
float NIS_y = _ekf_y.getPosNIS(_target_pos_rel_meas_NED.y, xy_pos_var);
|
|
if (MAX(NIS_x, NIS_y) < 3.0f || _outlier_reject_count >= 3) {
|
|
_outlier_reject_count = 0;
|
|
_ekf_x.fusePos(_target_pos_rel_meas_NED.x, xy_pos_var);
|
|
_ekf_y.fusePos(_target_pos_rel_meas_NED.y, xy_pos_var);
|
|
_last_update_ms = AP_HAL::millis();
|
|
} else {
|
|
_outlier_reject_count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// check EKF was properly initialized when the sensor detected a landing target
|
|
check_ekf_init_timeout();
|
|
|
|
// Output prediction
|
|
if (target_acquired()) {
|
|
_target_pos_rel_est_NE.x = _ekf_x.getPos();
|
|
_target_pos_rel_est_NE.y = _ekf_y.getPos();
|
|
_target_vel_rel_est_NE.x = _ekf_x.getVel();
|
|
_target_vel_rel_est_NE.y = _ekf_y.getVel();
|
|
|
|
run_output_prediction();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// check if EKF got the time to initialize when the landing target was first detected
|
|
// Expects sensor to update within EKF_INIT_SENSOR_MIN_UPDATE_MS milliseconds till EKF_INIT_TIME_MS milliseconds have passed
|
|
// after this period landing target estimates can be used by vehicle
|
|
void AC_PrecLand::check_ekf_init_timeout()
|
|
{
|
|
if (!target_acquired() && _estimator_initialized) {
|
|
// we have just got the target in sight
|
|
if (AP_HAL::millis()-_last_update_ms > EKF_INIT_SENSOR_MIN_UPDATE_MS) {
|
|
// we have lost the target, not enough readings to initialize the EKF
|
|
_estimator_initialized = false;
|
|
gcs().send_text(MAV_SEVERITY_CRITICAL, "PrecLand: Init Failed");
|
|
} else if (AP_HAL::millis()-_estimator_init_ms > EKF_INIT_TIME_MS) {
|
|
// the target has been visible for a while, EKF should now have initialized to a good value
|
|
_target_acquired = true;
|
|
gcs().send_text(MAV_SEVERITY_INFO, "PrecLand: Init Complete");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool AC_PrecLand::retrieve_los_meas(Vector3f& target_vec_unit_body)
|
|
{
|
|
if (_backend->have_los_meas() && _backend->los_meas_time_ms() != _last_backend_los_meas_ms) {
|
|
_last_backend_los_meas_ms = _backend->los_meas_time_ms();
|
|
_backend->get_los_body(target_vec_unit_body);
|
|
if (!is_zero(_yaw_align)) {
|
|
// Apply sensor yaw alignment rotation
|
|
target_vec_unit_body.rotate_xy(radians(_yaw_align*0.01f));
|
|
}
|
|
|
|
|
|
// rotate vector based on sensor oriention to get correct body frame vector
|
|
if (_orient != ROTATION_PITCH_270) {
|
|
// by default, the vector is constructed downwards in body frame
|
|
// hence, we do not do any rotation if the orientation is downwards
|
|
// if it is some other orientation, we first bring the vector to forward
|
|
// and then we rotate it to desired orientation
|
|
// because the rotations are measured with respect to a vector pointing towards front in body frame
|
|
// for eg, if orientation is back, i.e., ROTATION_YAW_180,
|
|
// the vector is first brought to front and then rotation by YAW 180 to take it to the back of vehicle
|
|
target_vec_unit_body.rotate(ROTATION_PITCH_90); // bring vector to front
|
|
target_vec_unit_body.rotate(_orient); // rotate it to desired orientation
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool AC_PrecLand::construct_pos_meas_using_rangefinder(float rangefinder_alt_m, bool rangefinder_alt_valid)
|
|
{
|
|
Vector3f target_vec_unit_body;
|
|
if (retrieve_los_meas(target_vec_unit_body)) {
|
|
const struct inertial_data_frame_s *inertial_data_delayed = (*_inertial_history)[0];
|
|
|
|
const bool target_vec_valid = target_vec_unit_body.projected(_approach_vector_body).dot(_approach_vector_body) > 0.0f;
|
|
const Vector3f target_vec_unit_ned = inertial_data_delayed->Tbn * target_vec_unit_body;
|
|
const Vector3f approach_vector_NED = inertial_data_delayed->Tbn * _approach_vector_body;
|
|
const bool alt_valid = (rangefinder_alt_valid && rangefinder_alt_m > 0.0f) || (_backend->distance_to_target() > 0.0f);
|
|
if (target_vec_valid && alt_valid) {
|
|
// distance to target and distance to target along approach vector
|
|
float dist_to_target, dist_to_target_along_av;
|
|
// figure out ned camera orientation w.r.t its offset
|
|
Vector3f cam_pos_ned;
|
|
if (!_cam_offset.get().is_zero()) {
|
|
// user has specifed offset for camera
|
|
// take its height into account while calculating distance
|
|
cam_pos_ned = inertial_data_delayed->Tbn * _cam_offset;
|
|
}
|
|
if (_backend->distance_to_target() > 0.0f) {
|
|
// sensor has provided distance to landing target
|
|
dist_to_target = _backend->distance_to_target();
|
|
} else {
|
|
// sensor only knows the horizontal location of the landing target
|
|
// rely on rangefinder for the vertical target
|
|
dist_to_target_along_av = MAX(rangefinder_alt_m - cam_pos_ned.projected(approach_vector_NED).length(), 0.0f);
|
|
dist_to_target = dist_to_target_along_av / target_vec_unit_ned.projected(approach_vector_NED).length();
|
|
}
|
|
|
|
// Compute camera position relative to IMU
|
|
const Vector3f accel_pos_ned = inertial_data_delayed->Tbn * AP::ins().get_imu_pos_offset(AP::ahrs().get_primary_accel_index());
|
|
const Vector3f cam_pos_ned_rel_imu = cam_pos_ned - accel_pos_ned;
|
|
|
|
// Compute target position relative to IMU
|
|
_target_pos_rel_meas_NED = (target_vec_unit_ned * dist_to_target) + cam_pos_ned_rel_imu;
|
|
|
|
// store the current relative down position so that if we need to retry landing, we know at this height landing target can be found
|
|
const AP_AHRS &_ahrs = AP::ahrs();
|
|
Vector3f pos_NED;
|
|
if (_ahrs.get_relative_position_NED_origin(pos_NED)) {
|
|
_last_target_pos_rel_origin_NED.z = pos_NED.z;
|
|
_last_vehicle_pos_NED = pos_NED;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void AC_PrecLand::run_output_prediction()
|
|
{
|
|
_target_pos_rel_out_NE = _target_pos_rel_est_NE;
|
|
_target_vel_rel_out_NE = _target_vel_rel_est_NE;
|
|
|
|
// Predict forward from delayed time horizon
|
|
for (uint8_t i=1; i<_inertial_history->available(); i++) {
|
|
const struct inertial_data_frame_s *inertial_data = (*_inertial_history)[i];
|
|
_target_vel_rel_out_NE.x -= inertial_data->correctedVehicleDeltaVelocityNED.x;
|
|
_target_vel_rel_out_NE.y -= inertial_data->correctedVehicleDeltaVelocityNED.y;
|
|
_target_pos_rel_out_NE.x += _target_vel_rel_out_NE.x * inertial_data->dt;
|
|
_target_pos_rel_out_NE.y += _target_vel_rel_out_NE.y * inertial_data->dt;
|
|
}
|
|
|
|
const AP_AHRS &_ahrs = AP::ahrs();
|
|
|
|
const Matrix3f& Tbn = (*_inertial_history)[_inertial_history->available()-1]->Tbn;
|
|
Vector3f accel_body_offset = AP::ins().get_imu_pos_offset(_ahrs.get_primary_accel_index());
|
|
|
|
// Apply position correction for CG offset from IMU
|
|
Vector3f imu_pos_ned = Tbn * accel_body_offset;
|
|
_target_pos_rel_out_NE.x += imu_pos_ned.x;
|
|
_target_pos_rel_out_NE.y += imu_pos_ned.y;
|
|
|
|
// Apply position correction for body-frame horizontal camera offset from CG, so that vehicle lands lens-to-target
|
|
Vector3f cam_pos_horizontal_ned = Tbn * Vector3f(_cam_offset.get().x, _cam_offset.get().y, 0);
|
|
_target_pos_rel_out_NE.x -= cam_pos_horizontal_ned.x;
|
|
_target_pos_rel_out_NE.y -= cam_pos_horizontal_ned.y;
|
|
|
|
// Apply velocity correction for IMU offset from CG
|
|
Vector3f vel_ned_rel_imu = Tbn * (_ahrs.get_gyro() % (-accel_body_offset));
|
|
_target_vel_rel_out_NE.x -= vel_ned_rel_imu.x;
|
|
_target_vel_rel_out_NE.y -= vel_ned_rel_imu.y;
|
|
|
|
// Apply land offset
|
|
Vector3f land_ofs_ned_m = _ahrs.get_rotation_body_to_ned() * Vector3f(_land_ofs_cm_x,_land_ofs_cm_y,0) * 0.01f;
|
|
_target_pos_rel_out_NE.x += land_ofs_ned_m.x;
|
|
_target_pos_rel_out_NE.y += land_ofs_ned_m.y;
|
|
|
|
// store the landing target as a offset from current position. This is used in landing retry
|
|
Vector2f last_target_loc_rel_origin_2d;
|
|
get_target_position_cm(last_target_loc_rel_origin_2d);
|
|
_last_target_pos_rel_origin_NED.x = last_target_loc_rel_origin_2d.x * 0.01f;
|
|
_last_target_pos_rel_origin_NED.y = last_target_loc_rel_origin_2d.y * 0.01f;
|
|
|
|
// record the last time there was a target output
|
|
_last_valid_target_ms = AP_HAL::millis();
|
|
}
|
|
|
|
// Write a precision landing entry
|
|
void AC_PrecLand::Write_Precland()
|
|
{
|
|
// exit immediately if not enabled
|
|
if (!enabled()) {
|
|
return;
|
|
}
|
|
|
|
Vector3f target_pos_meas;
|
|
Vector2f target_pos_rel;
|
|
Vector2f target_vel_rel;
|
|
get_target_position_relative_cm(target_pos_rel);
|
|
get_target_velocity_relative_cms(target_vel_rel);
|
|
get_target_position_measurement_cm(target_pos_meas);
|
|
|
|
const struct log_Precland pkt {
|
|
LOG_PACKET_HEADER_INIT(LOG_PRECLAND_MSG),
|
|
time_us : AP_HAL::micros64(),
|
|
healthy : healthy(),
|
|
target_acquired : target_acquired(),
|
|
pos_x : target_pos_rel.x,
|
|
pos_y : target_pos_rel.y,
|
|
vel_x : target_vel_rel.x,
|
|
vel_y : target_vel_rel.y,
|
|
meas_x : target_pos_meas.x,
|
|
meas_y : target_pos_meas.y,
|
|
meas_z : target_pos_meas.z,
|
|
last_meas : last_backend_los_meas_ms(),
|
|
ekf_outcount : ekf_outlier_count(),
|
|
estimator : (uint8_t)_estimator_type
|
|
};
|
|
AP::logger().WriteBlock(&pkt, sizeof(pkt));
|
|
}
|
|
|
|
// singleton instance
|
|
AC_PrecLand *AC_PrecLand::_singleton;
|
|
|
|
namespace AP {
|
|
|
|
AC_PrecLand *ac_precland()
|
|
{
|
|
return AC_PrecLand::get_singleton();
|
|
}
|
|
|
|
}
|