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

EKF2: range measurement rejection in rain/fog #23579

Merged
merged 4 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ void Ekf::controlRangeHaglFusion(const imuSample &imu_sample)
_range_sensor.setPitchOffset(_params.rng_sens_pitch);
_range_sensor.setCosMaxTilt(_params.range_cos_max_tilt);
_range_sensor.setQualityHysteresis(_params.range_valid_quality_s);
_range_sensor.setMaxFogDistance(_params.rng_fog);

_range_sensor.runChecks(imu_sample.time_us, _R_to_earth);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ void SensorRangeFinder::updateValidity(uint64_t current_time_us)

if (isTiltOk() && isDataInRange()) {
updateStuckCheck();
updateFogCheck(getDistBottom(), _sample.time_us);

if (!_is_stuck) {
if (!_is_stuck && !_is_blocked) {
_is_sample_valid = true;
_time_last_valid_us = _sample.time_us;
}
Expand Down Expand Up @@ -146,5 +147,23 @@ void SensorRangeFinder::updateStuckCheck()
}
}

void SensorRangeFinder::updateFogCheck(const float dist_bottom, const uint64_t time_us)
{
if (_max_fog_dist > 0.f && time_us - _time_last_valid_us < 1e6) {

const float median_dist = _median_dist.apply(dist_bottom);
const float factor = 2.f; // magic hardcoded factor

if (!_is_blocked && median_dist < _max_fog_dist && _prev_median_dist - median_dist > factor * _max_fog_dist) {
_is_blocked = true;

} else if (_is_blocked && median_dist > factor * _max_fog_dist) {
_is_blocked = false;
}

_prev_median_dist = median_dist;
}
}

} // namespace sensor
} // namespace estimator
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "Sensor.hpp"

#include <matrix/math.hpp>
#include <lib/mathlib/math/filter/MedianFilter.hpp>

namespace estimator
{
Expand Down Expand Up @@ -99,6 +100,8 @@ class SensorRangeFinder : public Sensor
_rng_valid_max_val = max_distance;
}

void setMaxFogDistance(float max_fog_dist) { _max_fog_dist = max_fog_dist; }

void setQualityHysteresis(float valid_quality_threshold_s)
{
_quality_hyst_us = uint64_t(valid_quality_threshold_s * 1e6f);
Expand Down Expand Up @@ -129,6 +132,7 @@ class SensorRangeFinder : public Sensor
bool isTiltOk() const { return _cos_tilt_rng_to_earth > _range_cos_max_tilt; }
bool isDataInRange() const;
void updateStuckCheck();
void updateFogCheck(const float dist_bottom, const uint64_t time_us);

rangeSample _sample{};

Expand Down Expand Up @@ -172,6 +176,14 @@ class SensorRangeFinder : public Sensor
*/
uint64_t _time_bad_quality_us{}; ///< timestamp at which range finder signal quality was 0 (used for hysteresis)
uint64_t _quality_hyst_us{}; ///< minimum duration during which the reported range finder signal quality needs to be non-zero in order to be declared valid (us)

/*
* Fog check
*/
bool _is_blocked{false};
float _max_fog_dist{0.f}; //< maximum distance at which the range finder could detect fog (m)
math::MedianFilter<float, 5> _median_dist{};
float _prev_median_dist;
haumarco marked this conversation as resolved.
Show resolved Hide resolved
};

} // namespace sensor
Expand Down
1 change: 1 addition & 0 deletions src/modules/ekf2/EKF/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ struct parameters {
float range_valid_quality_s{1.0f}; ///< minimum duration during which the reported range finder signal quality needs to be non-zero in order to be declared valid (s)
float range_cos_max_tilt{0.7071f}; ///< cosine of the maximum tilt angle from the vertical that permits use of range finder and flow data
float range_kin_consistency_gate{1.0f}; ///< gate size used by the range finder kinematic consistency check
float rng_fog{0.f}; ///< max distance which a blocked range sensor measures (fog, dirt) [m]

Vector3f rng_pos_body{}; ///< xyz position of range sensor in body frame (m)
#endif // CONFIG_EKF2_RANGE_FINDER
Expand Down
1 change: 1 addition & 0 deletions src/modules/ekf2/EKF/ekf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ void Ekf::reset()
_range_sensor.setPitchOffset(_params.rng_sens_pitch);
_range_sensor.setCosMaxTilt(_params.range_cos_max_tilt);
_range_sensor.setQualityHysteresis(_params.range_valid_quality_s);
_range_sensor.setMaxFogDistance(_params.rng_fog);
#endif // CONFIG_EKF2_RANGE_FINDER

_control_status.value = 0;
Expand Down
1 change: 1 addition & 0 deletions src/modules/ekf2/EKF2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ EKF2::EKF2(bool multi_mode, const px4::wq_config_t &config, bool replay_mode):
_param_ekf2_rng_a_igate(_params->range_aid_innov_gate),
_param_ekf2_rng_qlty_t(_params->range_valid_quality_s),
_param_ekf2_rng_k_gate(_params->range_kin_consistency_gate),
_param_ekf2_rng_fog(_params->rng_fog),
_param_ekf2_rng_pos_x(_params->rng_pos_body(0)),
_param_ekf2_rng_pos_y(_params->rng_pos_body(1)),
_param_ekf2_rng_pos_z(_params->rng_pos_body(2)),
Expand Down
1 change: 1 addition & 0 deletions src/modules/ekf2/EKF2.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ class EKF2 final : public ModuleParams, public px4::ScheduledWorkItem
(ParamExtFloat<px4::params::EKF2_RNG_A_IGATE>) _param_ekf2_rng_a_igate,
(ParamExtFloat<px4::params::EKF2_RNG_QLTY_T>) _param_ekf2_rng_qlty_t,
(ParamExtFloat<px4::params::EKF2_RNG_K_GATE>) _param_ekf2_rng_k_gate,
(ParamExtFloat<px4::params::EKF2_RNG_FOG>) _param_ekf2_rng_fog,
(ParamExtFloat<px4::params::EKF2_RNG_POS_X>) _param_ekf2_rng_pos_x,
(ParamExtFloat<px4::params::EKF2_RNG_POS_Y>) _param_ekf2_rng_pos_y,
(ParamExtFloat<px4::params::EKF2_RNG_POS_Z>) _param_ekf2_rng_pos_z,
Expand Down
13 changes: 13 additions & 0 deletions src/modules/ekf2/params_range_finder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,16 @@ parameters:
default: 0.0
unit: m
decimal: 3
EKF2_RNG_FOG:
description:
short: Maximum distance at which the range finder could detect fog (m)
long: Limit for fog detection. If the range finder measures a distance greater
than this value, the measurement is considered to not be blocked by fog or rain.
If there's a jump from larger than RNG_FOG to smaller than EKF2_RNG_FOG, the
measurement may gets rejected. 0 - disabled
type: float
default: 0.0
min: 0.0
max: 20.0
unit: m
decimal: 1
64 changes: 56 additions & 8 deletions src/modules/ekf2/test/test_SensorRangeFinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class SensorRangeFinderTest : public ::testing::Test
_range_finder.setPitchOffset(0.f);
_range_finder.setCosMaxTilt(0.707f);
_range_finder.setLimits(_min_range, _max_range);
_range_finder.setMaxFogDistance(2.f);
}

// Use this method to clean up any memory, network etc. after each test
Expand All @@ -60,20 +61,21 @@ class SensorRangeFinderTest : public ::testing::Test

protected:
SensorRangeFinder _range_finder{};
const rangeSample _good_sample{(uint64_t)2e6, 1.f, 100}; // {time_us, range, quality}
const rangeSample _good_sample{(uint64_t)2e6, 5.f, 100}; // {time_us, range, quality}
const float _min_range{0.5f};
const float _max_range{10.f};

void updateSensorAtRate(uint64_t duration_us, uint64_t dt_update_us, uint64_t dt_sensor_us);
void updateSensorAtRate(rangeSample sample, uint64_t duration_us, uint64_t dt_update_us, uint64_t dt_sensor_us);
void testTilt(const Eulerf &euler, bool should_pass);
};

void SensorRangeFinderTest::updateSensorAtRate(uint64_t duration_us, uint64_t dt_update_us, uint64_t dt_sensor_us)
void SensorRangeFinderTest::updateSensorAtRate(rangeSample sample, uint64_t duration_us, uint64_t dt_update_us,
uint64_t dt_sensor_us)
{
const Dcmf attitude{Eulerf(0.f, 0.f, 0.f)};

rangeSample new_sample = _good_sample;
uint64_t t_now_us = _good_sample.time_us;
rangeSample new_sample = sample;
uint64_t t_now_us = sample.time_us;

for (int i = 0; i < int(duration_us / dt_update_us); i++) {
t_now_us += dt_update_us;
Expand Down Expand Up @@ -307,7 +309,7 @@ TEST_F(SensorRangeFinderTest, continuity)
const uint64_t dt_update_us = 10e3;
uint64_t dt_sensor_us = 4e6;
uint64_t duration_us = 8e6;
updateSensorAtRate(duration_us, dt_update_us, dt_sensor_us);
updateSensorAtRate(_good_sample, duration_us, dt_update_us, dt_sensor_us);

// THEN: the data should be marked as unhealthy
// Note that it also fails the out-of-date test here
Expand All @@ -317,14 +319,14 @@ TEST_F(SensorRangeFinderTest, continuity)
// AND WHEN: the data rate is acceptable
dt_sensor_us = 3e5;
duration_us = 5e5;
updateSensorAtRate(duration_us, dt_update_us, dt_sensor_us);
updateSensorAtRate(_good_sample, duration_us, dt_update_us, dt_sensor_us);

// THEN: it should still fail until the filter converge
// to the new datarate
EXPECT_FALSE(_range_finder.isDataHealthy());
EXPECT_FALSE(_range_finder.isHealthy());

updateSensorAtRate(duration_us, dt_update_us, dt_sensor_us);
updateSensorAtRate(_good_sample, duration_us, dt_update_us, dt_sensor_us);
EXPECT_TRUE(_range_finder.isDataHealthy());
EXPECT_TRUE(_range_finder.isHealthy());
}
Expand All @@ -345,3 +347,49 @@ TEST_F(SensorRangeFinderTest, distBottom)
_range_finder.runChecks(sample.time_us, attitude20);
EXPECT_FLOAT_EQ(_range_finder.getDistBottom(), sample.rng * cosf(-0.35));
}

TEST_F(SensorRangeFinderTest, blockedByFog)
{
// WHEN: sensor is not blocked by fog
const Dcmf attitude{Eulerf(0.f, 0.f, 0.f)};
const uint64_t dt_update_us = 10e3;
uint64_t dt_sensor_us = 3e5;
uint64_t duration_us = 5e5;

updateSensorAtRate(_good_sample, duration_us, dt_update_us, dt_sensor_us);
// THEN: the data should be marked as healthy
EXPECT_TRUE(_range_finder.isDataHealthy());
EXPECT_TRUE(_range_finder.isHealthy());


// WHEN: sensor is then blocked by fog
// range jumps to value below 2m
uint64_t t_now_us = _range_finder.getSampleAddress()->time_us;
rangeSample sample{t_now_us, 1.f, 100};
updateSensorAtRate(sample, duration_us, dt_update_us, dt_sensor_us);

// THEN: the data should be marked as unhealthy
EXPECT_FALSE(_range_finder.isDataHealthy());
EXPECT_FALSE(_range_finder.isHealthy());

// WHEN: the sensor is not blocked by fog anymore
sample.rng = 5.f;
updateSensorAtRate(sample, duration_us, dt_update_us, dt_sensor_us);

// THEN: the data should be marked as healthy again
EXPECT_TRUE(_range_finder.isDataHealthy());
EXPECT_TRUE(_range_finder.isHealthy());

// WHEN: the sensor is is not jumping to a value below 2m
while (sample.rng > _min_range) {
sample.time_us += dt_update_us;
_range_finder.setSample(sample);
_range_finder.runChecks(sample.time_us, attitude);
sample.rng -= 0.5f;
}

// THEN: the data should still be marked as healthy
EXPECT_TRUE(_range_finder.isDataHealthy());
EXPECT_TRUE(_range_finder.isHealthy());

}
Loading