diff --git a/src/deck/drivers/src/lpsTdoa2Tag.c b/src/deck/drivers/src/lpsTdoa2Tag.c index be324d96a1..49f4abfdb7 100644 --- a/src/deck/drivers/src/lpsTdoa2Tag.c +++ b/src/deck/drivers/src/lpsTdoa2Tag.c @@ -38,7 +38,6 @@ #include "estimator.h" #include "estimator_kalman.h" -#include "outlierFilter.h" #include "physicalConstants.h" @@ -152,12 +151,10 @@ static void enqueueTDOA(uint8_t anchorA, uint8_t anchorB, double distanceDiff) { .anchorPosition[1] = options->anchorPosition[anchorB] }; - if (outlierFilterValidateTdoa(&tdoa)) { - if (options->combinedAnchorPositionOk || - (options->anchorPosition[anchorA].timestamp && options->anchorPosition[anchorB].timestamp)) { - stats.packetsToEstimator++; - estimatorKalmanEnqueueTDOA(&tdoa); - } + if (options->combinedAnchorPositionOk || + (options->anchorPosition[anchorA].timestamp && options->anchorPosition[anchorB].timestamp)) { + stats.packetsToEstimator++; + estimatorKalmanEnqueueTDOA(&tdoa); } } diff --git a/src/modules/interface/estimator_kalman.h b/src/modules/interface/estimator_kalman.h index 2ea39776a5..6b973a4ba7 100644 --- a/src/modules/interface/estimator_kalman.h +++ b/src/modules/interface/estimator_kalman.h @@ -80,4 +80,4 @@ void estimatorKalmanSetShift(float deltax, float deltay); void estimatorKalmanGetEstimatedPos(point_t* pos); -#endif // __ESTIMATOR_KALMAN_H__ \ No newline at end of file +#endif // __ESTIMATOR_KALMAN_H__ diff --git a/src/utils/interface/outlierFilter.h b/src/modules/interface/outlierFilter.h similarity index 84% rename from src/utils/interface/outlierFilter.h rename to src/modules/interface/outlierFilter.h index c70e3219a2..3296e0fe4a 100644 --- a/src/utils/interface/outlierFilter.h +++ b/src/modules/interface/outlierFilter.h @@ -29,7 +29,9 @@ #include "stabilizer_types.h" -bool outlierFilterValidateTdoa(tdoaMeasurement_t* tdoa); +bool outlierFilterValidateTdoaSimple(const tdoaMeasurement_t* tdoa); +bool outlierFilterVaildateTdoaSteps(const tdoaMeasurement_t* tdoa, const float error, const vector_t* jacobian, const point_t* estPos); + void outlierFilterReset(); #endif // __OUTLIER_FILTER_H__ diff --git a/src/modules/interface/stabilizer_types.h b/src/modules/interface/stabilizer_types.h index a97133b291..d3eea2be29 100644 --- a/src/modules/interface/stabilizer_types.h +++ b/src/modules/interface/stabilizer_types.h @@ -52,6 +52,7 @@ struct vec3_s { float z; }; +typedef struct vec3_s vector_t; typedef struct vec3_s point_t; typedef struct vec3_s velocity_t; typedef struct vec3_s acc_t; diff --git a/src/modules/src/estimator_kalman.c b/src/modules/src/estimator_kalman.c index 286550acda..c9e9bb06fb 100644 --- a/src/modules/src/estimator_kalman.c +++ b/src/modules/src/estimator_kalman.c @@ -56,6 +56,8 @@ */ #include "estimator_kalman.h" +#include "outlierFilter.h" + #include "stm32f4xx.h" @@ -1019,39 +1021,28 @@ static void stateEstimatorUpdateWithTDOA(tdoaMeasurement_t *tdoa) float h[STATE_DIM] = {0}; arm_matrix_instance_f32 H = {1, STATE_DIM, h}; - // We want to do - // h[STATE_X] = (dx1 / d1 - dx0 / d0); - // h[STATE_Y] = (dy1 / d1 - dy0 / d0); - // h[STATE_Z] = (dz1 / d1 - dz0 / d0); - // but have to handle divide by zero - - if (d1 != 0.0f) - { - h[STATE_X] = dx1 / d1; - h[STATE_Y] = dy1 / d1; - h[STATE_Z] = dz1 / d1; - } - else - { - h[STATE_X] = 1.0f; - h[STATE_Y] = 0.0f; - h[STATE_Z] = 0.0f; - } - - if (d0 != 0.0f) - { - h[STATE_X] = h[STATE_X] - dx0 / d0; - h[STATE_Y] = h[STATE_Y] - dy0 / d0; - h[STATE_Z] = h[STATE_Z] - dz0 / d0; - } - else - { - h[STATE_X] = h[STATE_X] - 0.0f; - h[STATE_Y] = h[STATE_Y] - 1.0f; - h[STATE_Z] = h[STATE_Z] - 0.0f; + if ((d0 != 0.0f) && (d1 != 0.0f)) { + h[STATE_X] = (dx1 / d1 - dx0 / d0); + h[STATE_Y] = (dy1 / d1 - dy0 / d0); + h[STATE_Z] = (dz1 / d1 - dz0 / d0); + + vector_t jacobian = { + .x = h[STATE_X], + .y = h[STATE_Y], + .z = h[STATE_Z], + }; + + point_t estimatedPosition = { + .x = S[STATE_X], + .y = S[STATE_Y], + .z = S[STATE_Z], + }; + + bool sampleIsGood = outlierFilterVaildateTdoaSteps(tdoa, error, &jacobian, &estimatedPosition); + if (sampleIsGood) { + stateEstimatorScalarUpdate(&H, error, tdoa->stdDev); + } } - - stateEstimatorScalarUpdate(&H, error, tdoa->stdDev); } tdoaCount++; diff --git a/src/modules/src/outlierFilter.c b/src/modules/src/outlierFilter.c new file mode 100644 index 0000000000..f5c904c80b --- /dev/null +++ b/src/modules/src/outlierFilter.c @@ -0,0 +1,160 @@ +/** + * || ____ _ __ + * +------+ / __ )(_) /_______________ _____ ___ + * | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \ + * +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/ + * || || /_____/_/\__/\___/_/ \__,_/ /___/\___/ + * + * Crazyflie control firmware + * + * Copyright (C) 2011-2018 Bitcraze AB + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, in version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * outlierFilter.c: Outlier rejection filter for the LPS system + */ + +#include +#include "outlierFilter.h" +#include "stabilizer_types.h" +#include "log.h" + +#define BUCKET_ACCEPTANCE_LEVEL 2 +#define MAX_BUCKET_FILL 10 +#define FILTER_CLOSE_DELAY_COUNT 30 + +static float acceptanceLevel = 0.0; +static float errorDistance; +static int filterCloseDelayCounter = 0; +static int previousFilterIndex = 0; + +typedef struct { + float acceptanceLevel; + int bucket; +} filterLevel_t; + +#define FILTER_LEVELS 5 +#define FILTER_NONE FILTER_LEVELS +filterLevel_t filterLevels[FILTER_LEVELS] = { + {.acceptanceLevel = 0.4}, + {.acceptanceLevel = 0.8}, + {.acceptanceLevel = 1.2}, + {.acceptanceLevel = 1.6}, + {.acceptanceLevel = 2.0}, +}; + + +static bool isDistanceDiffSmallerThanDistanceBetweenAnchors(const tdoaMeasurement_t* tdoa); +static float distanceSq(const point_t* a, const point_t* b); +static float sq(float a) {return a * a;} +static void addToBucket(filterLevel_t* filter); +static void removeFromBucket(filterLevel_t* filter); +static int updateBuckets(float errorDistance); + + + +bool outlierFilterValidateTdoaSimple(const tdoaMeasurement_t* tdoa) { + return isDistanceDiffSmallerThanDistanceBetweenAnchors(tdoa); +} + +bool outlierFilterVaildateTdoaSteps(const tdoaMeasurement_t* tdoa, const float error, const vector_t* jacobian, const point_t* estPos) { + bool sampleIsGood = false; + + if (isDistanceDiffSmallerThanDistanceBetweenAnchors(tdoa)) { + float errorBaseDistance = sqrtf(powf(jacobian->x, 2) + powf(jacobian->y, 2) + powf(jacobian->z, 2)); + errorDistance = fabsf(error / errorBaseDistance); + + int filterIndex = updateBuckets(errorDistance); + + if (filterIndex > previousFilterIndex) { + filterCloseDelayCounter = FILTER_CLOSE_DELAY_COUNT; + } else if (filterIndex < previousFilterIndex) { + if (filterCloseDelayCounter > 0) { + filterCloseDelayCounter--; + filterIndex = previousFilterIndex; + } + } + previousFilterIndex = filterIndex; + + if (filterIndex == FILTER_NONE) { + // Lost tracking, open up to let the kalman filter converge + acceptanceLevel = 100.0; + sampleIsGood = true; + } else { + acceptanceLevel = filterLevels[filterIndex].acceptanceLevel; + if (errorDistance < acceptanceLevel) { + sampleIsGood = true; + } + } + } + + return sampleIsGood; +} + +void outlierFilterReset() { + // Nothing here +} + +static bool isDistanceDiffSmallerThanDistanceBetweenAnchors(const tdoaMeasurement_t* tdoa) { + float anchorDistanceSq = distanceSq(&tdoa->anchorPosition[0], &tdoa->anchorPosition[1]); + float distanceDiffSq = sq(tdoa->distanceDiff); + return (distanceDiffSq < anchorDistanceSq); +} + +static float distanceSq(const point_t* a, const point_t* b) { + return sq(a->x - b->x) + sq(a->y - b->y) + sq(a->z - b->z); +} + + +static void addToBucket(filterLevel_t* filter) { + if (filter->bucket < MAX_BUCKET_FILL) { + filter->bucket++; + } +} + +static void removeFromBucket(filterLevel_t* filter) { + if (filter->bucket > 0) { + filter->bucket--; + } +} + +static int updateBuckets(float errorDistance) { + int filterIndex = FILTER_NONE; + + for (int i = FILTER_LEVELS - 1; i >= 0; i--) { + filterLevel_t* filter = &filterLevels[i]; + + if (errorDistance < filter->acceptanceLevel) { + removeFromBucket(filter); + } else { + addToBucket(filter); + } + + if (filter->bucket < BUCKET_ACCEPTANCE_LEVEL) { + filterIndex = i; + } + } + + return filterIndex; +} + +LOG_GROUP_START(outlierf) + LOG_ADD(LOG_INT32, bucket0, &filterLevels[0].bucket) + LOG_ADD(LOG_INT32, bucket1, &filterLevels[1].bucket) + LOG_ADD(LOG_INT32, bucket2, &filterLevels[2].bucket) + LOG_ADD(LOG_INT32, bucket3, &filterLevels[3].bucket) + LOG_ADD(LOG_INT32, bucket4, &filterLevels[4].bucket) + LOG_ADD(LOG_FLOAT, accLev, &acceptanceLevel) + LOG_ADD(LOG_FLOAT, errD, &errorDistance) + +LOG_GROUP_STOP(outlierf) diff --git a/src/utils/src/outlierFilter.c b/src/utils/src/outlierFilter.c deleted file mode 100644 index 1ebe279d12..0000000000 --- a/src/utils/src/outlierFilter.c +++ /dev/null @@ -1,51 +0,0 @@ -/** - * || ____ _ __ - * +------+ / __ )(_) /_______________ _____ ___ - * | 0xBC | / __ / / __/ ___/ ___/ __ `/_ / / _ \ - * +------+ / /_/ / / /_/ /__/ / / /_/ / / /_/ __/ - * || || /_____/_/\__/\___/_/ \__,_/ /___/\___/ - * - * Crazyflie control firmware - * - * Copyright (C) 2011-2018 Bitcraze AB - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, in version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * outlierFilter.c: Outlier rejection filter for the LPS system - */ - -#include -#include "outlierFilter.h" - -static bool isDistanceDiffSmallerThanDistanceBetweenAnchors(tdoaMeasurement_t* tdoa); -static float distanceSq(const point_t* a, const point_t* b); -static float sq(float a) {return a * a;} - - -bool outlierFilterValidateTdoa(tdoaMeasurement_t* tdoa) { - return isDistanceDiffSmallerThanDistanceBetweenAnchors(tdoa); -} - -void outlierFilterReset() { - // Nothing here -} - -static bool isDistanceDiffSmallerThanDistanceBetweenAnchors(tdoaMeasurement_t* tdoa) { - float anchorDistanceSq = distanceSq(&tdoa->anchorPosition[0], &tdoa->anchorPosition[1]); - float distanceDiffSq = sq(tdoa->distanceDiff); - return (distanceDiffSq < anchorDistanceSq); -} - -static float distanceSq(const point_t* a, const point_t* b) { - return sq(a->x - b->x) + sq(a->y - b->y) + sq(a->z - b->z); -} diff --git a/src/utils/src/tdoa/tdoaEngine.c b/src/utils/src/tdoa/tdoaEngine.c index d9720f9244..42cd8e75f0 100644 --- a/src/utils/src/tdoa/tdoaEngine.c +++ b/src/utils/src/tdoa/tdoaEngine.c @@ -50,7 +50,6 @@ The implementation must handle #include "tdoaEngine.h" #include "tdoaStats.h" -#include "outlierFilter.h" #include "clockCorrectionEngine.h" #include "physicalConstants.h" @@ -77,7 +76,6 @@ static void enqueueTDOA(const tdoaAnchorContext_t* anchorACtx, const tdoaAnchorC }; if (tdoaStorageGetAnchorPosition(anchorACtx, &tdoa.anchorPosition[0]) && tdoaStorageGetAnchorPosition(anchorBCtx, &tdoa.anchorPosition[1])) { - if (outlierFilterValidateTdoa(&tdoa)) { stats->packetsToEstimator++; engineState->sendTdoaToEstimator(&tdoa); @@ -89,7 +87,6 @@ static void enqueueTDOA(const tdoaAnchorContext_t* anchorACtx, const tdoaAnchorC if (idB == stats->anchorId && idA == stats->remoteAnchorId) { stats->tdoa = -distanceDiff; } - } } } diff --git a/test/deck/drivers/src/TestLpsTdoa2Tag.c b/test/deck/drivers/src/TestLpsTdoa2Tag.c index 366a674232..8b71ae0e27 100644 --- a/test/deck/drivers/src/TestLpsTdoa2Tag.c +++ b/test/deck/drivers/src/TestLpsTdoa2Tag.c @@ -9,7 +9,6 @@ #include "mock_cfassert.h" #include "mock_estimator_kalman.h" #include "mock_locodeck.h" -#include "mock_outlierFilter.h" #include "dw1000Mocks.h" #include "freertosMocks.h" @@ -394,7 +393,6 @@ void testMissingTimestampInhibitsClockDriftCalculationInFirstIteration() { mockKalmanEstimator(0, 5, expectedDiff); mockKalmanEstimator(5, 0, -expectedDiff); mockKalmanEstimator(0, 5, expectedDiff); - outlierFilterValidateTdoa_IgnoreAndReturn(true); // Test uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); @@ -453,7 +451,6 @@ void testMissingPacketAnchorToAnchorInhibitsDiffCalculation() { // Not called // mockKalmanEstimator(5, 0, -expectedDiff); mockKalmanEstimator(0, 5, expectedDiff); - outlierFilterValidateTdoa_IgnoreAndReturn(true); // Test uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); @@ -512,7 +509,6 @@ void testMissingAnchorToAnchorDistanceInhibitsDiffCalculation() { // Not called // mockKalmanEstimator(5, 0, -expectedDiff); mockKalmanEstimator(0, 5, expectedDiff); - outlierFilterValidateTdoa_IgnoreAndReturn(true); // Test uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); @@ -570,7 +566,6 @@ void testMissingPacketPacketAnchorToAnchorInhibitsDiffCalculation() { // Not called // mockKalmanEstimator(5, 0, -expectedDiff); mockKalmanEstimator(0, 5, expectedDiff); - outlierFilterValidateTdoa_IgnoreAndReturn(true); // Test uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); @@ -628,7 +623,6 @@ void testMissingPacketPacketAnchorToAnchorInhibitsDiffCalculationWhenSequenceNrW // Not called // mockKalmanEstimator(5, 0, -expectedDiff); mockKalmanEstimator(0, 5, expectedDiff); - outlierFilterValidateTdoa_IgnoreAndReturn(true); // Test uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); @@ -695,7 +689,6 @@ void testMissingPacketAnchorToTagInhibitsDiffCalculation() { // Not called since previous packet from same anchor was lost // mockKalmanEstimator(5, 0, -expectedDiff); - outlierFilterValidateTdoa_IgnoreAndReturn(true); // Test uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); @@ -709,58 +702,6 @@ void testMissingPacketAnchorToTagInhibitsDiffCalculation() { // Nothing here, verification in mocks } -void testDataNotSentToKalmanFilterWhenOutlierDetected() { - // Fixture - // Two anchors, separated by 1.0m - // Distance from A0 to tag is 2.0m - // Distance from A5 to tag is 2.5m - - // Ideal times in universal clock - uint64_t timeA0ToTag = time2m; - uint64_t timeA5ToTag = time2_5m; - uint64_t timeA0ToA5 = time1m; - - mockMessageFromAnchor(5, iTxTime0_5 + timeA5ToTag, - (uint8_t[]) {10, 0, 0, 0, 0, 20, 0, 0}, - (uint64_t[]){NS, NS, NS, NS, NS, iTxTime0_5, NS, NS}, - (uint64_t[]){NS, NS, NS, NS, NS, NS, NS, NS}); - - mockMessageFromAnchor(0, iTxTime1_0 + timeA0ToTag, - (uint8_t[]) {11, 0, 0, 0, 0, 20, 0, 0}, - (uint64_t[]){iTxTime1_0, NS, NS, NS, NS, iTxTime0_5 + timeA0ToA5, NS, NS}, - (uint64_t[]){NS, NS, NS, NS, NS, timeA0ToA5, NS, NS}); - - mockMessageFromAnchor(5, iTxTime1_5 + timeA5ToTag, - (uint8_t[]) {11, 0, 0, 0, 0, 21, 0, 0}, - (uint64_t[]){iTxTime1_0 + timeA0ToA5, NS, NS, NS, NS, iTxTime1_5, NS, NS}, - (uint64_t[]){timeA0ToA5, NS, NS, NS, NS, NS, NS, NS}); - - mockMessageFromAnchor(0, iTxTime2_0 + timeA0ToTag, - (uint8_t[]) {12, 0, 0, 0, 0, 21, 0, 0}, - (uint64_t[]){iTxTime2_0, NS, NS, NS, NS, iTxTime1_5 + timeA0ToA5, NS, NS}, - (uint64_t[]){NS, NS, NS, NS, NS, timeA0ToA5, NS, NS}); - - mockMessageFromAnchor(5, iTxTime2_5 + timeA5ToTag, - (uint8_t[]) {12, 0, 0, 0, 0, 22, 0, 0}, - (uint64_t[]){iTxTime2_0 + timeA0ToA5, NS, NS, NS, NS, iTxTime2_5, NS, NS}, - (uint64_t[]){timeA0ToA5, NS, NS, NS, NS, NS, NS, NS}); - - - // The outlier filter reports that the data is identified as outliers and should - // not be sent to the kalman filter - outlierFilterValidateTdoa_IgnoreAndReturn(false); - - // Test - uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); - uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); - uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); - uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); - uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); - - // Assert - // Nothing here, verification in mocks -} - void testPacketReceivedEventShouldSetTheRadioInReceiveMode() { // Fixture // mockRadioSetToReceiveMode() called as part of mockMessageFromAnchor() @@ -938,7 +879,6 @@ void testDifferenceOfDistanceNotPushedInKalmanIfAnchorsPositionIsInValid() { (uint64_t[]){timeA0ToA1, NS, NS, NS, NS, NS, NS, NS}); // The measurement should not be pushed in the kalman filter - outlierFilterValidateTdoa_IgnoreAndReturn(true); // Test uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); @@ -1175,7 +1115,6 @@ void verifyDifferenceOfDistanceWithNoClockDriftButConfigurableClockOffset(uint64 // Only the last message will create calls to the estimator. The two first are discarded due to missing data. mockKalmanEstimator(0, 1, expectedDiff); - outlierFilterValidateTdoa_IgnoreAndReturn(true); // Test uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); @@ -1236,7 +1175,6 @@ void verifyDifferenceOfDistanceWithTwoAnchors3FramesWithClockDrift(float driftTa mockKalmanEstimator(0, 5, expectedDiff); mockKalmanEstimator(5, 0, -expectedDiff); mockKalmanEstimator(0, 5, expectedDiff); - outlierFilterValidateTdoa_IgnoreAndReturn(true); // Test uwbTdoa2TagAlgorithm.onEvent(&dev, eventPacketReceived); diff --git a/test/deck/drivers/src/TestOutlierFilter.c b/test/modules/src/TestOutlierFilter.c similarity index 86% rename from test/deck/drivers/src/TestOutlierFilter.c rename to test/modules/src/TestOutlierFilter.c index dbf0aa021f..563208447d 100644 --- a/test/deck/drivers/src/TestOutlierFilter.c +++ b/test/modules/src/TestOutlierFilter.c @@ -32,7 +32,7 @@ void testThatSamplesAreAcceptedWhenTdoaIsCloserThanDistanceBetweenAnchors() { bool expected = true; // Test - bool actual = outlierFilterValidateTdoa(&tdoa); + bool actual = outlierFilterValidateTdoaSimple(&tdoa); // Assert TEST_ASSERT_EQUAL(actual, expected); @@ -45,7 +45,7 @@ void testThatSamplesAreRejectedWhenTdoaIsGreaterThanDistanceBetweenAnchors() { bool expected = false; // Test - bool actual = outlierFilterValidateTdoa(&tdoa); + bool actual = outlierFilterValidateTdoaSimple(&tdoa); // Assert TEST_ASSERT_EQUAL(actual, expected); @@ -58,7 +58,7 @@ void testThatSamplesAreRejectedWhenTdoaIsGreaterButNegativeThanDistanceBetweenAn bool expected = false; // Test - bool actual = outlierFilterValidateTdoa(&tdoa); + bool actual = outlierFilterValidateTdoaSimple(&tdoa); // Assert TEST_ASSERT_EQUAL(actual, expected); diff --git a/tools/test/gcc.yml b/tools/test/gcc.yml index f3f9370fcb..61dbdb21a8 100644 --- a/tools/test/gcc.yml +++ b/tools/test/gcc.yml @@ -34,6 +34,7 @@ compiler: - 'src/config/' - 'src/drivers/interface/' - 'src/modules/interface/' + - 'src/modules/src/' - 'src/lib/FreeRTOS/portable/GCC/ARM_CM4F/' - 'src/hal/interface/' - 'test/testSupport/'