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"); }