Range check cleanup (#782)

* EKF: centralize range finder tilt check

* Ekf-control: do not double check for terrain estimate validity

isRangeAidSuitable can only return true if the terrain estimate is valid
so there is no need for an additional check

* range_finder_checks: restructure the checks to avoid early returns

There is now only one clear path that can lead to the validity being
true.
Furthermore, if the _rng_hgt_valid is true, we can trust it and we don't
need for additional checks such as tilt.

The case where we need to provide fake measurements because the drone is
on the ground and the range finder data is bad is already handled
in "controlHeightFusion" so there is no need to hack the range finder
checks with that.

* Add Sensor and SensorRangeFinder classes

The purpose is to encapsulate the checks for each sensor in a dedicated
class with the same interface

* SensorRangeFinder: encapsulate in estimator::sensor namespace

* EKF: rename _sensor_rng to _range_sensor

* Range checks: include limits in valid range

* RangeChecks: update comment in the continuity checks

* RangeChecks: move more low-level checks in functions

Also move setTilt out of the terrain estimator, this is anyway protected internally
to not compute cos/sin if the parameter did not change.

* Sensor: remove unused virtual functions

Those are not required yet but can still be added later

* SensorRangeFinder: re-organise member variables

Also rename getRangeToEarth to getCosTilt

* SensorRangeFinder: split setSensorTilt and setCosMaxTilt functions

* SensorRangeFinder: Add a few unit tests

- good data
- tilt exceeded
- max range exceeded

* SensorRangeFinder: set hysteresis in us instead of ms

* SensorRangeFinder: Add more tests

* SensorRangeFinder: update continuity, hysteresis and stuck tests

* SensorRangeFinder: rename variables

* SensorRangeFinder: get rid of "delayed" specification

From the SensorRangeFinder class point of view, it's not relevant to
know if the data is delayed or not

* SensorRangeFinder: move time_last_valid out of stuck check

* SensorRangeFinder: rename file names to sensor_range_finder

* SensorRangeFinder: address Kamil's comments

* SensorRangeFinder: Add more tilt tests

* SensorRangeFinder: store current tilt offset

This is to avoid recomputing cos/sin functions at each loop
This commit is contained in:
Mathieu Bresciani
2020-04-03 08:28:07 +02:00
committed by GitHub
parent 544385fd8c
commit 0623b2b02d
15 changed files with 747 additions and 263 deletions
+30 -37
View File
@@ -116,20 +116,24 @@ void Ekf::controlFusionModes()
_delta_time_baro_us = _baro_sample_delayed.time_us - _delta_time_baro_us;
}
// calculate 2,2 element of rotation matrix from sensor frame to earth frame
// this is required for use of range finder and flow data
_R_rng_to_earth_2_2 = _R_to_earth(2, 0) * _sin_tilt_rng + _R_to_earth(2, 2) * _cos_tilt_rng;
{
// Get range data from buffer and check validity
_range_data_ready = _range_buffer.pop_first_older_than(_imu_sample_delayed.time_us, &_range_sample_delayed);
const bool is_rng_data_ready = _range_buffer.pop_first_older_than(_imu_sample_delayed.time_us, _range_sensor.getSampleAddress());
_range_sensor.setDataReadiness(is_rng_data_ready);
_range_sensor.runChecks(_imu_sample_delayed.time_us, _R_to_earth);
updateRangeDataValidity();
// update range sensor angle parameters in case they have changed
_range_sensor.setTiltOffset(_params.rng_sens_pitch);
_range_sensor.setCosMaxTilt(_params.range_cos_max_tilt);
}
if (_range_data_ready && _rng_hgt_valid) {
if (_range_sensor.isDataHealthy()) {
// correct the range data for position offset relative to the IMU
Vector3f pos_offset_body = _params.rng_pos_body - _params.imu_pos_body;
Vector3f pos_offset_earth = _R_to_earth * pos_offset_body;
_range_sample_delayed.rng += pos_offset_earth(2) / _R_rng_to_earth_2_2;
_range_sensor.setRange(_range_sensor.getRange() + pos_offset_earth(2) / _range_sensor.getCosTilt());
}
// We don't fuse flow data immediately because we have to wait for the mid integration point to fall behind the fusion time horizon.
@@ -853,7 +857,7 @@ void Ekf::controlHeightSensorTimeouts()
} else if (_control_status.flags.rng_hgt) {
if (_rng_hgt_valid) {
if (_range_sensor.isHealthy()) {
request_height_reset = true;
ECL_WARN_TIMESTAMPED("rng hgt timeout - reset to rng hgt");
@@ -933,7 +937,7 @@ void Ekf::checkVerticalAccelerationHealth()
void Ekf::controlHeightFusion()
{
checkRangeAidSuitability();
_range_aid_mode_selected = (_params.range_aid == 1) && isRangeAidSuitable();
const bool do_range_aid = (_params.range_aid == 1) && isRangeAidSuitable();
bool fuse_height = false;
@@ -943,22 +947,17 @@ void Ekf::controlHeightFusion()
// FALLTHROUGH
case VDIST_SENSOR_BARO:
if (_range_aid_mode_selected && _range_data_ready && _rng_hgt_valid) {
if (do_range_aid && _range_sensor.isDataHealthy()) {
setControlRangeHeight();
fuse_height = true;
// we have just switched to using range finder, calculate height sensor offset such that current
// measurement matches our current height estimate
if (_control_status_prev.flags.rng_hgt != _control_status.flags.rng_hgt) {
if (isTerrainEstimateValid()) {
_hgt_sensor_offset = _terrain_vpos;
} else {
_hgt_sensor_offset = _R_rng_to_earth_2_2 * _range_sample_delayed.rng + _state.pos(2);
}
_hgt_sensor_offset = _terrain_vpos;
}
} else if (!_range_aid_mode_selected && _baro_data_ready && !_baro_hgt_faulty) {
} else if (!do_range_aid && _baro_data_ready && !_baro_hgt_faulty) {
setControlBaroHeight();
fuse_height = true;
@@ -990,9 +989,9 @@ void Ekf::controlHeightFusion()
break;
case VDIST_SENSOR_RANGE:
if (_range_data_ready && _rng_hgt_valid) {
if (_range_sensor.isDataHealthy()) {
setControlRangeHeight();
fuse_height = _range_data_ready;
fuse_height = true;
if (_control_status_prev.flags.rng_hgt != _control_status.flags.rng_hgt) {
// we have just switched to using range finder, calculate height sensor offset such that current
@@ -1002,7 +1001,7 @@ void Ekf::controlHeightFusion()
_hgt_sensor_offset = _terrain_vpos;
} else if (_control_status.flags.in_air) {
_hgt_sensor_offset = _R_rng_to_earth_2_2 * _range_sample_delayed.rng + _state.pos(2);
_hgt_sensor_offset = _range_sensor.getCosTilt() * _range_sensor.getRange() + _state.pos(2);
} else {
_hgt_sensor_offset = _params.rng_gnd_clearance;
@@ -1025,22 +1024,17 @@ void Ekf::controlHeightFusion()
case VDIST_SENSOR_GPS:
// Determine if GPS should be used as the height source
if (_range_aid_mode_selected && _range_data_ready && _rng_hgt_valid) {
if (do_range_aid && _range_sensor.isDataHealthy()) {
setControlRangeHeight();
fuse_height = true;
// we have just switched to using range finder, calculate height sensor offset such that current
// measurement matches our current height estimate
if (_control_status_prev.flags.rng_hgt != _control_status.flags.rng_hgt) {
if (isTerrainEstimateValid()) {
_hgt_sensor_offset = _terrain_vpos;
} else {
_hgt_sensor_offset = _R_rng_to_earth_2_2 * _range_sample_delayed.rng + _state.pos(2);
}
_hgt_sensor_offset = _terrain_vpos;
}
} else if (!_range_aid_mode_selected && _gps_data_ready && !_gps_hgt_intermittent && _gps_checks_passed) {
} else if (!do_range_aid && _gps_data_ready && !_gps_hgt_intermittent && _gps_checks_passed) {
setControlGPSHeight();
fuse_height = true;
@@ -1103,14 +1097,14 @@ void Ekf::controlHeightFusion()
}
if (isTimedOut(_time_last_hgt_fuse, 2 * RNG_MAX_INTERVAL) && _control_status.flags.rng_hgt
&& (!_range_data_ready || !_rng_hgt_valid)) {
&& (!_range_sensor.isDataHealthy())) {
// If we are supposed to be using range finder data as the primary height sensor, have missed or rejected measurements
// and are on the ground, then synthesise a measurement at the expected on ground value
if (!_control_status.flags.in_air) {
_range_sample_delayed.rng = _params.rng_gnd_clearance;
_range_sample_delayed.time_us = _imu_sample_delayed.time_us;
_range_sensor.setRange(_params.rng_gnd_clearance);
_range_sensor.setDataReadiness(true);
_range_sensor.setValidity(true); // bypass the checks
}
fuse_height = true;
@@ -1163,15 +1157,14 @@ void Ekf::controlHeightFusion()
fuseVerticalPosition(_gps_pos_innov,gps_hgt_innov_gate,
gps_hgt_obs_var, _gps_pos_innov_var,_gps_pos_test_ratio);
} else if (_control_status.flags.rng_hgt && (_R_rng_to_earth_2_2 > _params.range_cos_max_tilt)) {
// TODO: Tilt check does not belong here, should not set fuse height to true if tilted
} else if (_control_status.flags.rng_hgt) {
Vector2f rng_hgt_innov_gate;
Vector3f rng_hgt_obs_var;
// use range finder with tilt correction
_rng_hgt_innov(2) = _state.pos(2) - (-math::max(_range_sample_delayed.rng * _R_rng_to_earth_2_2,
_rng_hgt_innov(2) = _state.pos(2) - (-math::max(_range_sensor.getRange() * _range_sensor.getCosTilt(),
_params.rng_gnd_clearance)) - _hgt_sensor_offset;
// observation variance - user parameter defined
rng_hgt_obs_var(2) = fmaxf((sq(_params.range_noise) + sq(_params.range_noise_scaler * _range_sample_delayed.rng)) * sq(_R_rng_to_earth_2_2), 0.01f);
rng_hgt_obs_var(2) = fmaxf((sq(_params.range_noise) + sq(_params.range_noise_scaler * _range_sensor.getRange())) * sq(_range_sensor.getCosTilt()), 0.01f);
// innovation gate size
rng_hgt_innov_gate(1) = fmaxf(_params.range_innov_gate, 1.0f);
// fuse height information
@@ -1197,7 +1190,7 @@ void Ekf::controlHeightFusion()
void Ekf::checkRangeAidSuitability()
{
if (_control_status.flags.in_air
&& _rng_hgt_valid
&& _range_sensor.isHealthy()
&& isTerrainEstimateValid()
&& isHorizontalAidingActive()) {
// check if we can use range finder measurements to estimate height, use hysteresis to avoid rapid switching