From 989a56e96d867bac595dcd74b8f3dd07bad5d887 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 25 Aug 2022 11:08:44 -0700 Subject: [PATCH] [link-metrics] update the scaling of link margin and RSSI metrics (#8078) This commit updates the scaling of the link margin and RSSI metrics. The metric values are scaled when appended in the message (in a Report sub-TLV) or when they are read back from received message. The stored value in `MetricsValues` are changed to be always the actual metric value (not the scaled value). This ensures that the value are stored in proper int type (e.g., RSSI is `int8` vs the scaled value which is `[0,255]`). Methods are added to perform the scaling which now rounds to closest integer ensuring the reverse scaling gives back the original value. Unit test is added to validate the scaling methods. --- src/core/common/num_utils.hpp | 16 ++++++ src/core/thread/link_metrics.cpp | 89 +++++++++++++++++++++++-------- src/core/thread/link_metrics.hpp | 12 +++++ tests/unit/test_link_quality.cpp | 52 ++++++++++++++++++ tests/unit/test_serial_number.cpp | 13 +++++ 5 files changed, 159 insertions(+), 23 deletions(-) diff --git a/src/core/common/num_utils.hpp b/src/core/common/num_utils.hpp index 998347041..3d479b9a9 100644 --- a/src/core/common/num_utils.hpp +++ b/src/core/common/num_utils.hpp @@ -171,6 +171,22 @@ template <> inline int ThreeWayCompare(bool aFirst, bool aSecond) return (aFirst == aSecond) ? 0 : (aFirst ? 1 : -1); } +/** + * This template function divides two numbers and rounds the result to the closest integer. + * + * @tparam IntType The integer type. + * + * @param[in] aDividend The dividend value. + * @param[in] aDivisor The divisor value. + * + * @return The result of division and rounding to the closest integer. + * + */ +template inline IntType DivideAndRoundToClosest(IntType aDividend, IntType aDivisor) +{ + return (aDividend + (aDivisor / 2)) / aDivisor; +} + } // namespace ot #endif // NUM_UTILS_HPP_ diff --git a/src/core/thread/link_metrics.cpp b/src/core/thread/link_metrics.cpp index 4f6be421d..90ac4872d 100644 --- a/src/core/thread/link_metrics.cpp +++ b/src/core/thread/link_metrics.cpp @@ -40,6 +40,8 @@ #include "common/instance.hpp" #include "common/locator_getters.hpp" #include "common/log.hpp" +#include "common/num_utils.hpp" +#include "common/numeric_limits.hpp" #include "mac/mac.hpp" #include "thread/link_metrics_tlvs.hpp" #include "thread/neighbor_table.hpp" @@ -251,13 +253,10 @@ Error LinkMetrics::AppendReport(Message &aMessage, const Message &aRequestMessag if (queryId == kQueryIdSingleProbe) { - values.mPduCountValue = aRequestMessage.GetPsduCount(); - values.mLqiValue = aRequestMessage.GetAverageLqi(); - // Linearly scale Link Margin from [0, 130] to [0, 255] - values.mLinkMarginValue = Get().ComputeLinkMargin(aRequestMessage.GetAverageRss()) * 255 / 130; - // Linearly scale rss from [-130, 0] to [0, 255] - values.mRssiValue = (aRequestMessage.GetAverageRss() + 130) * 255 / 130; - + values.mPduCountValue = aRequestMessage.GetPsduCount(); + values.mLqiValue = aRequestMessage.GetAverageLqi(); + values.mLinkMarginValue = Get().ComputeLinkMargin(aRequestMessage.GetAverageRss()); + values.mRssiValue = aRequestMessage.GetAverageRss(); SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, values)); } else @@ -275,12 +274,10 @@ Error LinkMetrics::AppendReport(Message &aMessage, const Message &aRequestMessag else { values.SetMetrics(seriesInfo->GetLinkMetrics()); - values.mPduCountValue = seriesInfo->GetPduCount(); - values.mLqiValue = seriesInfo->GetAverageLqi(); - // Linearly scale Link Margin from [0, 130] to [0, 255] - values.mLinkMarginValue = Get().ComputeLinkMargin(seriesInfo->GetAverageRss()) * 255 / 130; - // Linearly scale RSSI from [-130, 0] to [0, 255] - values.mRssiValue = (seriesInfo->GetAverageRss() + 130) * 255 / 130; + values.mPduCountValue = seriesInfo->GetPduCount(); + values.mLqiValue = seriesInfo->GetAverageLqi(); + values.mLinkMarginValue = Get().ComputeLinkMargin(seriesInfo->GetAverageRss()); + values.mRssiValue = seriesInfo->GetAverageRss(); SuccessOrExit(error = AppendReportSubTlvToMessage(aMessage, values)); } } @@ -483,15 +480,13 @@ void LinkMetrics::HandleReport(const Message & aMessage, case TypeIdFlags::kLinkMargin: values.mMetrics.mLinkMargin = true; - // Reverse operation for linear scale, map from [0, 255] to [0, 130] - values.mLinkMarginValue = reportTlv.GetMetricsValue8() * 130 / 255; + values.mLinkMarginValue = ScaleRawValueToLinkMargin(reportTlv.GetMetricsValue8()); LogDebg(" - Margin: %d (dB) (Exponential Moving Average)", values.mLinkMarginValue); break; case TypeIdFlags::kRssi: values.mMetrics.mRssi = true; - // Reverse operation for linear scale, map from [0, 255] to [-130, 0] - values.mRssiValue = reportTlv.GetMetricsValue8() * 130 / 255 - 130; + values.mRssiValue = ScaleRawValueToRssi(reportTlv.GetMetricsValue8()); LogDebg(" - RSSI: %d (dBm) (Exponential Moving Average)", values.mRssiValue); break; } @@ -559,13 +554,11 @@ void LinkMetrics::ProcessEnhAckIeData(const uint8_t *aData, uint8_t aLength, con } if (values.GetMetrics().mLinkMargin && idx < aLength) { - // Reverse operation for linear scale, map from [0, 255] to [0, 130] - values.mLinkMarginValue = aData[idx++] * 130 / 255; + values.mLinkMarginValue = ScaleRawValueToLinkMargin(aData[idx++]); } if (values.GetMetrics().mRssi && idx < aLength) { - // Reverse operation for linear scale, map from [0, 255] to [-130, 0] - values.mRssiValue = aData[idx++] * 130 / 255 - 130; + values.mRssiValue = ScaleRawValueToRssi(aData[idx++]); } mEnhAckProbingIeReportCallback(aNeighbor.GetRloc16(), &aNeighbor.GetExtAddress(), &values, @@ -786,14 +779,14 @@ Error LinkMetrics::AppendReportSubTlvToMessage(Message &aMessage, const MetricsV if (aValues.mMetrics.mLinkMargin) { reportTlv.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kLinkMargin)); - reportTlv.SetMetricsValue8(aValues.mLinkMarginValue); + reportTlv.SetMetricsValue8(ScaleLinkMarginToRawValue(aValues.mLinkMarginValue)); SuccessOrExit(error = reportTlv.AppendTo(aMessage)); } if (aValues.mMetrics.mRssi) { reportTlv.SetMetricsTypeId(TypeIdFlags(TypeIdFlags::kRssi)); - reportTlv.SetMetricsValue8(aValues.mRssiValue); + reportTlv.SetMetricsValue8(ScaleRssiToRawValue(aValues.mRssiValue)); SuccessOrExit(error = reportTlv.AppendTo(aMessage)); } @@ -801,6 +794,56 @@ exit: return error; } +uint8_t LinkMetrics::ScaleLinkMarginToRawValue(uint8_t aLinkMargin) +{ + // Linearly scale Link Margin from [0, 130] to [0, 255]. + // `kMaxLinkMargin = 130`. + + uint16_t value; + + value = Min(aLinkMargin, kMaxLinkMargin); + value = value * NumericLimits::kMax; + value = DivideAndRoundToClosest(value, kMaxLinkMargin); + + return static_cast(value); +} + +uint8_t LinkMetrics::ScaleRawValueToLinkMargin(uint8_t aRawValue) +{ + // Scale back raw value of [0, 255] to Link Margin from [0, 130]. + + uint16_t value = aRawValue; + + value = value * kMaxLinkMargin; + value = DivideAndRoundToClosest(value, NumericLimits::kMax); + return static_cast(value); +} + +uint8_t LinkMetrics::ScaleRssiToRawValue(int8_t aRssi) +{ + // Linearly scale RSSI from [-130, 0] to [0, 255]. + // `kMinRssi = -130`, `kMaxRssi = 0`. + + int32_t value = aRssi; + + value = Clamp(value, kMinRssi, kMaxRssi) - kMinRssi; + value = value * NumericLimits::kMax; + value = DivideAndRoundToClosest(value, kMaxRssi - kMinRssi); + + return static_cast(value); +} + +int8_t LinkMetrics::ScaleRawValueToRssi(uint8_t aRawValue) +{ + int32_t value = aRawValue; + + value = value * (kMaxRssi - kMinRssi); + value = DivideAndRoundToClosest(value, NumericLimits::kMax); + value += kMinRssi; + + return static_cast(value); +} + } // namespace LinkMetrics } // namespace ot diff --git a/src/core/thread/link_metrics.hpp b/src/core/thread/link_metrics.hpp index 643ff08a0..f5d08f6d9 100644 --- a/src/core/thread/link_metrics.hpp +++ b/src/core/thread/link_metrics.hpp @@ -57,6 +57,7 @@ namespace ot { class Neighbor; +class UnitTester; namespace LinkMetrics { @@ -76,6 +77,7 @@ namespace LinkMetrics { class LinkMetrics : public InstanceLocator, private NonCopyable { friend class ot::Neighbor; + friend class ot::UnitTester; public: typedef otLinkMetricsReportCallback ReportCallback; @@ -272,6 +274,11 @@ private: static constexpr uint8_t kSeriesIdAllSeries = 255; // This series ID represents all series. static constexpr uint8_t kLinkProbeMaxLen = 64; // Max length of data payload in Link Probe TLV. + // Constants for scaling Link Margin and RSSI to raw value + static constexpr uint8_t kMaxLinkMargin = 130; + static constexpr int32_t kMinRssi = -130; + static constexpr int32_t kMaxRssi = 0; + Error SendLinkMetricsQuery(const Ip6::Address &aDestination, uint8_t aSeriesId, const TypeIdFlags * aTypeIdFlags, @@ -292,6 +299,11 @@ private: Metrics & aMetrics); static Error AppendReportSubTlvToMessage(Message &aMessage, const MetricsValues &aValues); + static uint8_t ScaleLinkMarginToRawValue(uint8_t aLinkMargin); + static uint8_t ScaleRawValueToLinkMargin(uint8_t aRawValue); + static uint8_t ScaleRssiToRawValue(int8_t aRssi); + static int8_t ScaleRawValueToRssi(uint8_t aRawValue); + ReportCallback mReportCallback; void * mReportCallbackContext; MgmtResponseCallback mMgmtResponseCallback; diff --git a/tests/unit/test_link_quality.cpp b/tests/unit/test_link_quality.cpp index f9162e366..eede26385 100644 --- a/tests/unit/test_link_quality.cpp +++ b/tests/unit/test_link_quality.cpp @@ -31,6 +31,7 @@ #include "common/array.hpp" #include "common/code_utils.hpp" +#include "thread/link_metrics.hpp" #include "thread/link_quality.hpp" namespace ot { @@ -481,6 +482,53 @@ void TestSuccessRateTracker(void) } } +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE + +class UnitTester +{ +public: + static void TestLinkMetricsScaling(void) + { + printf("\nTestLinkMetricsScaling\n"); + + // Test Link Margin scaling from [0,130] -> [0, 255] + + for (uint8_t linkMargin = 0; linkMargin <= 130; linkMargin++) + { + double scaled = 255.0 / 130.0 * linkMargin; + uint8_t scaledAsU8 = static_cast(scaled + 0.5); + + printf("\nLinkMargin : %-3u -> Scaled : %.1f (rounded:%u)", linkMargin, scaled, scaledAsU8); + + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleLinkMarginToRawValue(linkMargin) == scaledAsU8); + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleRawValueToLinkMargin(scaledAsU8) == linkMargin); + } + + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleLinkMarginToRawValue(131) == 255); + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleLinkMarginToRawValue(150) == 255); + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleLinkMarginToRawValue(255) == 255); + + // Test RSSI scaling from [-130, 0] -> [0, 255] + + for (int8_t rssi = -128; rssi <= 0; rssi++) + { + double scaled = 255.0 / 130.0 * (rssi + 130.0); + uint8_t scaledAsU8 = static_cast(scaled + 0.5); + + printf("\nRSSI : %-3d -> Scaled :%.1f (rounded:%u)", rssi, scaled, scaledAsU8); + + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleRssiToRawValue(rssi) == scaledAsU8); + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleRawValueToRssi(scaledAsU8) == rssi); + } + + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleRssiToRawValue(1) == 255); + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleRssiToRawValue(10) == 255); + VerifyOrQuit(LinkMetrics::LinkMetrics::ScaleRssiToRawValue(127) == 255); + } +}; + +#endif + } // namespace ot int main(void) @@ -488,6 +536,10 @@ int main(void) ot::TestRssAveraging(); ot::TestLinkQualityCalculations(); ot::TestSuccessRateTracker(); +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE + ot::UnitTester::TestLinkMetricsScaling(); +#endif + printf("\nAll tests passed\n"); return 0; } diff --git a/tests/unit/test_serial_number.cpp b/tests/unit/test_serial_number.cpp index 35cecc2ab..67aeebfcb 100644 --- a/tests/unit/test_serial_number.cpp +++ b/tests/unit/test_serial_number.cpp @@ -118,6 +118,19 @@ void TestNumUtils(void) VerifyOrQuit(ThreeWayCompare(true, false) > 0); VerifyOrQuit(ThreeWayCompare(false, true) < 0); + VerifyOrQuit(DivideAndRoundToClosest(2, 1) == 2); + VerifyOrQuit(DivideAndRoundToClosest(1, 3) == 0); + VerifyOrQuit(DivideAndRoundToClosest(1, 2) == 1); + VerifyOrQuit(DivideAndRoundToClosest(2, 3) == 1); + VerifyOrQuit(DivideAndRoundToClosest(3, 2) == 2); + VerifyOrQuit(DivideAndRoundToClosest(4, 2) == 2); + + VerifyOrQuit(DivideAndRoundToClosest(0, 10) == 0); + VerifyOrQuit(DivideAndRoundToClosest(4, 10) == 0); + VerifyOrQuit(DivideAndRoundToClosest(5, 10) == 1); + VerifyOrQuit(DivideAndRoundToClosest(9, 10) == 1); + VerifyOrQuit(DivideAndRoundToClosest(10, 10) == 1); + printf("TestNumUtils() passed\n"); }