From cb3389882746bfb1b60a5188904a853c972bf263 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 27 Jun 2016 14:15:11 -0700 Subject: [PATCH] Enhance the calculation of link quality metrics (#197) This commit adds a new class `LinkQualityInfo` which stores all relevant information about quality of a link, including average received signal strength (RSS), link margin, and link quality value. The RSS average is obtained using an adaptive exponential moving average filter. A unit-test is also added for the new class. The Neighbor class now includes an instance of LinkQualityInfo. The RSS average is updated from 'mac.cpp' whenever a valid/verified packet is received from the corresponding neighbor. The mesh_forwarder, mle_router, and mle modules are modified to use the new methods from the newly added class. This change addresses issue #175. --- include/openthread.h | 1 + src/core/Makefile.am | 2 + src/core/mac/mac.cpp | 5 + src/core/thread/link_quality.cpp | 286 ++++++++++++++++++++++++ src/core/thread/link_quality.hpp | 242 ++++++++++++++++++++ src/core/thread/mesh_forwarder.cpp | 2 +- src/core/thread/mle.cpp | 27 +-- src/core/thread/mle.hpp | 10 - src/core/thread/mle_router.cpp | 56 +++-- src/core/thread/thread_netif.hpp | 2 +- src/core/thread/topology.hpp | 12 +- tests/unit/Makefile.am | 4 + tests/unit/test_link_quality.cpp | 348 +++++++++++++++++++++++++++++ 13 files changed, 935 insertions(+), 62 deletions(-) create mode 100644 src/core/thread/link_quality.cpp create mode 100644 src/core/thread/link_quality.hpp create mode 100644 tests/unit/test_link_quality.cpp diff --git a/include/openthread.h b/include/openthread.h index 3bf9789fb..74f410041 100644 --- a/include/openthread.h +++ b/include/openthread.h @@ -95,6 +95,7 @@ extern "C" { * @defgroup core-tasklet Tasklet * @defgroup core-timer Timer * @defgroup core-udp UDP + * @defgroup core-link-quality Link Quality * * @} * diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 27432b426..b32c2cecc 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -57,6 +57,7 @@ libopenthread_a_SOURCES = \ net/udp6.cpp \ thread/address_resolver.cpp \ thread/key_manager.cpp \ + thread/link_quality.cpp \ thread/lowpan.cpp \ thread/mesh_forwarder.cpp \ thread/mle.cpp \ @@ -96,6 +97,7 @@ noinst_HEADERS = \ net/udp6.hpp \ thread/address_resolver.hpp \ thread/key_manager.hpp \ + thread/link_quality.hpp \ thread/lowpan.hpp \ thread/mesh_forwarder.hpp \ thread/mle.hpp \ diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index a7e1d88ec..8581c1003 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -867,6 +867,11 @@ void Mac::ReceiveDoneTask(Frame *aFrame, ThreadError aError) // Security Processing SuccessOrExit(error = ProcessReceiveSecurity(*aFrame, srcaddr, neighbor)); + if (neighbor != NULL) + { + neighbor->mLinkInfo.AddRss(aFrame->mPower); + } + switch (mState) { case kStateActiveScan: diff --git a/src/core/thread/link_quality.cpp b/src/core/thread/link_quality.cpp new file mode 100644 index 000000000..deeb67615 --- /dev/null +++ b/src/core/thread/link_quality.cpp @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements link quality information processing and storage. + */ + +#include +#include + +#include +#include +#include + +namespace Thread { + +enum +{ + kDefaultNoiseFloor = -100, // Default noise floor used if no average value is available. +}; + +// This array gives the decimal point digits representing 0/8, 1/8, ..., 7/8 (it does not include the '.'). +static const char *const kLinkQualityDecimalDigitsString[8] = +{ + // 0/8, 1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8 + "000", "125", "250", "375", "500", "625", "750", "875" +}; + +const char LinkQualityInfo::kUnknownRssString[] = "Unknown RSS"; + +static LinkQualityInfo sNoiseFloorAverage; // Store the noise floor average. + +//------------------------------------------------------------------------------- + +LinkQualityInfo::LinkQualityInfo(void) +{ + Clear(); +} + +void LinkQualityInfo::Clear(void) +{ + mRssAverage = 0; + mCount = 0; + mLinkQuality = 0; +} + +void LinkQualityInfo::AddRss(int8_t anRss) +{ + uint16_t newValue; + uint16_t oldAverage; + + // Restrict/Cap the RSS value to the closed range [0, -128] so the value can fit in 8 bits. + + if (anRss > 0) + { + anRss = 0; + } + + // Multiply the the RSS value by a precision multiple (currently -8). + + newValue = -anRss; + newValue <<= kRssAveragePrecisionMultipleBitShift; + + oldAverage = mRssAverage; + + if (mCount >= kRssCountForWeightCoefficientOneEighth) + { + // New average = old average * 7/8 + new value * 1/8 + mRssAverage = ((oldAverage << 3) - oldAverage + newValue) >> 3; + } + else if (mCount >= kRssCountForWeightCoefficientOneFourth) + { + // New average = old average * 3/4 + new value * 1/4 + mRssAverage = ((oldAverage << 2) - oldAverage + newValue) >> 2; + } + else if (mCount >= kRssCountForWeightCoefficientOneHalf) + { + // New average = old average * 1/2 + new value * 1/2 + mRssAverage = (oldAverage + newValue) >> 1; + } + else + { + mRssAverage = newValue; + } + + if (mCount < kRssCountMax) + { + mCount++; + } + + UpdateLinkQuality(); +} + +int8_t LinkQualityInfo::GetAverageRss(void) const +{ + int8_t average = kUnknownRss; + + if (mCount != 0) + { + average = -(static_cast(mRssAverage >> kRssAveragePrecisionMultipleBitShift)); + + // Check for round up (e.g. average of -71.5 --> -72) + + if ((mRssAverage & kRssAveragePrecisionMultipleBitMask) >= (kRssAveragePrecisionMultiple >> 1)) + { + average--; + } + } + + return average; +} + +uint16_t LinkQualityInfo::GetAverageRssAsEncodedWord(void) const +{ + return mRssAverage; +} + +ThreadError LinkQualityInfo::GetAverageRssAsString(char *aCharBuffer, size_t aBufferLen) const +{ + ThreadError error = kThreadError_None; + int charsWritten = 0; + + if (mCount == 0) + { + VerifyOrExit(aBufferLen >= sizeof(kUnknownRssString), error = kThreadError_NoBufs); + + strncpy(aCharBuffer, kUnknownRssString, aBufferLen); + } + else + { + charsWritten = snprintf(aCharBuffer, aBufferLen, "%d.%s dBm", + -(mRssAverage >> kRssAveragePrecisionMultipleBitShift), + kLinkQualityDecimalDigitsString[mRssAverage & kRssAveragePrecisionMultipleBitMask]); + + VerifyOrExit(charsWritten >= 0, error = kThreadError_NoBufs); + + VerifyOrExit(static_cast(charsWritten) < aBufferLen, error = kThreadError_NoBufs); + } + +exit: + return error; +} + +uint8_t LinkQualityInfo::GetLinkMargin(void) const +{ + return ConvertRssToLinkMargin(GetAverageRss()); +} + +uint8_t LinkQualityInfo::GetLinkQuality(void) +{ + UpdateLinkQuality(); + + return mLinkQuality; +} + +void LinkQualityInfo::UpdateLinkQuality(void) +{ + if (mCount != 0) + { + mLinkQuality = CalculateLinkQuality(GetLinkMargin(), mLinkQuality); + } + else + { + mLinkQuality = CalculateLinkQuality(GetLinkMargin(), kNoLastLinkQualityValue); + } +} + +uint8_t LinkQualityInfo::ConvertRssToLinkMargin(int8_t anRss) +{ + int8_t linkMargin = anRss - GetAverageNoiseFloor(); + + if (linkMargin < 0 || anRss == kUnknownRss) + { + linkMargin = 0; + } + + return static_cast(linkMargin); +} + +uint8_t LinkQualityInfo::ConvertLinkMarginToLinkQuality(uint8_t aLinkMargin) +{ + return CalculateLinkQuality(aLinkMargin, kNoLastLinkQualityValue); +} + +uint8_t LinkQualityInfo::ConvertRssToLinkQuality(int8_t anRss) +{ + return ConvertLinkMarginToLinkQuality(ConvertRssToLinkMargin(anRss)); +} + +uint8_t LinkQualityInfo::CalculateLinkQuality(uint8_t aLinkMargin, uint8_t aLastLinkQuality) +{ + uint8_t threshold1, threshold2, threshold3; + uint8_t linkQuality = 0; + + threshold1 = kLinkMarginThresholdForLinkQuality1; + threshold2 = kLinkMarginThresholdForLinkQuality2; + threshold3 = kLinkMarginThresholdForLinkQuality3; + + // Apply the hysteresis threshold based on the last link quality value. + + switch (aLastLinkQuality) + { + case 0: + threshold1 += kLinkMarginHysteresisThreshold; + + // Intentional fall-through to next case. + + case 1: + threshold2 += kLinkMarginHysteresisThreshold; + + // Intentional fall-through to next case. + + case 2: + threshold3 += kLinkMarginHysteresisThreshold; + + // Intentional fall-through to next case. + + default: + break; + } + + if (aLinkMargin > threshold3) + { + linkQuality = 3; + } + else if (aLinkMargin > threshold2) + { + linkQuality = 2; + } + else if (aLinkMargin > threshold1) + { + linkQuality = 1; + } + + return linkQuality; +} + +int8_t GetAverageNoiseFloor(void) +{ + int8_t averageNoiseFloor = sNoiseFloorAverage.GetAverageRss(); + + if (averageNoiseFloor == LinkQualityInfo::kUnknownRss) + { + averageNoiseFloor = kDefaultNoiseFloor; + } + + return averageNoiseFloor; +} + +void AddNoiseFloor(int8_t aNoiseFloor) +{ + sNoiseFloorAverage.AddRss(aNoiseFloor); +} + +void ClearNoiseFloorAverage(void) +{ + sNoiseFloorAverage.Clear(); +} + +} // namespace Thread diff --git a/src/core/thread/link_quality.hpp b/src/core/thread/link_quality.hpp new file mode 100644 index 000000000..75fc3e2a5 --- /dev/null +++ b/src/core/thread/link_quality.hpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for storing and processing link quality information. + */ + +#ifndef LINK_QUALITY_HPP_ +#define LINK_QUALITY_HPP_ + +#include + +namespace Thread { + +/** + * @addtogroup core-link-quality + * + * @brief + * This module includes definitions for Thread link quality metrics. + * + * @{ + */ + +/** + * This class encapsulates/stores all relevant information about quality of a link, including average received signal + * strength (RSS), link margin and link quality value (value in 0-3). The average is obtained using an adaptive + * exponential moving average filter. + * + */ +class LinkQualityInfo +{ +public: + enum + { + kUnknownRss = 127, ///< Indicates an unknown signal strength value or average. + }; + + /** + * This constructor initializes an instance of the LinkQualityInfo class. + * + */ + LinkQualityInfo(void); + + /** + * This method clears the all the data in this instance. + * + */ + void Clear(void); + + /** + * This method adds a new received signal strength (RSS) value to the average. + * + * @param[in] anRss A new received signal strength value (in dBm) to be added to the average. + * + */ + void AddRss(int8_t anRss); + + /** + * This method returns the current average signal strength value. + * + * @returns The current average value or @c kUnknownRss if no average is available. + * + */ + int8_t GetAverageRss(void) const; + + /** + * This method returns an encoded version of current average signal strength value. The encoded value is the + * average multiplied by a precision factor (currently -8). + * + * @returns The current average multiplied by precision factor or zero if no average is available. + * + */ + uint16_t GetAverageRssAsEncodedWord(void) const; + + /** + * This method provides the current average received signal strength (RSS) as a human-readable string (e.g., + * "-80.375 dBm"). If the average is unknown, "unknown RSS" is used/returned in the buffer. + * + * @param[out] aCharBuffer A char buffer to store the string corresponding to current average value. + * @param[in] aBufferLen The char buffer length. + * + * @retval kThreadError_None Successfully formed the string in the given char buffer. + * @retval kThreadError_NoBuf The string representation of the average value could not fit in the given buffer. + * + */ + ThreadError GetAverageRssAsString(char *aCharBuffer, size_t aBufferLen) const; + + /** + * This method returns the link margin. The link margin is calculated using the link's current average received + * signal strength (RSS) and average noise floor. + * + * @returns Link margin derived from average received signal strength and average noise floor. + * + */ + uint8_t GetLinkMargin(void) const; + + /** + * Returns the current one-way link quality value. The link quality value is a number 0-3. + * + * The link quality is calculated by comparing the current link margin with a set of thresholds (per Thread spec). + * More specifically, link margin > 20 dB gives link quality 3, link margin > 10 dB gives link quality 2, + * link margin > 2 dB gives link quality 1, and link margin below or equal to 2 dB yields link quality of 0. + * + * In order to ensure that a link margin near the boundary of two different link quality values does not cause + * frequent changes, a hysteresis of 2 dB is applied when determining the link quality. For example, the average + * link margin must be at least 12 dB to change a quality 1 link to a quality 2 link. + * + * @returns The current link quality value (value 0-3 as per Thread specification). + */ + uint8_t GetLinkQuality(void); + + /** + * This method converts a received signal strength value to a link margin value. + * + * @param[in] anRss The received signal strength value (in dBm). + * + * @returns The link margin value. + * + */ + static uint8_t ConvertRssToLinkMargin(int8_t anRss); + + /** + * This method converts a link margin value to a link quality value. + * + * @param[in] aLinkMargin The Link Margin in dB. + * + * @returns The link quality value (0-3). + * + */ + static uint8_t ConvertLinkMarginToLinkQuality(uint8_t aLinkMargin); + + /** + * This method converts a received signal strength value to a link quality value. + * + * @param[in] anRss The received signal strength value (in dBm). + * + * @returns The link quality value (0-3). + * + */ + static uint8_t ConvertRssToLinkQuality(int8_t anRss); + +private: + enum + { + // Constants for obtaining link quality from link margin: + + kLinkMarginThresholdForLinkQuality3 = 20, // Link margin threshold for quality 3 link. + kLinkMarginThresholdForLinkQuality2 = 10, // Link margin threshold for quality 2 link. + kLinkMarginThresholdForLinkQuality1 = 2, // Link margin threshold for quality 1 link. + kLinkMarginHysteresisThreshold = 2, // Link margin hysteresis threshold. + + kNoLastLinkQualityValue = 0xff, // Used to indicate that there is no previous/last link quality. + + // Constants related to RSS adaptive exponential moving average filter: + + kRssAveragePrecisionMultipleBitShift = 3, // Precision multiple for RSS average (1 << PrecisionBitShift). + kRssAveragePrecisionMultiple = (1 << kRssAveragePrecisionMultipleBitShift), + kRssAveragePrecisionMultipleBitMask = (kRssAveragePrecisionMultiple - 1), + + kRssCountMax = 7, // mCount max limit value. + + kRssCountForWeightCoefficientOneEighth = 5, // mCount threshold to use average weight coefficient of 1/8. + kRssCountForWeightCoefficientOneFourth = 2, // mCount threshold to use average weight coefficient of 1/4. + kRssCountForWeightCoefficientOneHalf = 1, // mCount threshold to use average weight coefficient of 1/2. + }; + + /* Private method to update the mLinkQuality value. This is called when a new RSS value is added to average + * or when GetLinkQuality() is invoked. + */ + void UpdateLinkQuality(void); + + /* Static private method to calculate the link quality from a given link margin while taking into account the last + * link quality value and adding the hysteresis value to the thresholds. If there is no previous value for link + * quality, the constant kNoLastLinkQualityValue should be passed as the second argument. + * + */ + static uint8_t CalculateLinkQuality(uint8_t aLinkMargin, uint8_t aLastLinkQuality); + + static const char kUnknownRssString[]; // Constant string used when RSS average is unknown. + + // All data should fit into a 16-bit (uint16_t) value. + + uint16_t mRssAverage : 11; // The encoded average signal strength value (stored as rss times precision multiple). + uint8_t mCount : 3; // Number of RSS values added to average so far (limited to kRssCountMax). + uint8_t mLinkQuality : 2; // Current link quality value (0-3). +}; + +/** + * This function returns the current average noise floor level (in dBm). + * + * @returns The current average noise floor level (in dBm). + */ +int8_t GetAverageNoiseFloor(void); + +/** + * This method adds a new noise floor value (in dBm) to the running average. + * + * @param[in] aNoiseFloor A new noise floor value (in dBm) to be added to the average. + * + */ +void AddNoiseFloor(int8_t aNoiseFloor); + +/** + * This method clears the current average noise floor value. + * + */ +void ClearNoiseFloorAverage(void); + + +/** + * @} + */ + +} // namespace Thread + +#endif // LINK_QUALITY_HPP_ diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index 32c52460f..7f7d39132 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -1015,7 +1015,7 @@ void MeshForwarder::HandleReceivedFrame(Mac::Frame &aFrame, ThreadError aError) } SuccessOrExit(aFrame.GetDstAddr(macDest)); - messageInfo.mLinkMargin = aFrame.GetPower() - -100; + messageInfo.mRss = aFrame.GetPower(); messageInfo.mLqi = aFrame.GetLqi(); messageInfo.mLinkSecurity = aFrame.GetSecurityEnabled(); diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 6db7e42c8..a70d0e799 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -1554,30 +1554,11 @@ exit: return error; } -uint8_t Mle::LinkMarginToQuality(uint8_t aLinkMargin) -{ - if (aLinkMargin > 20) - { - return 3; - } - else if (aLinkMargin > 10) - { - return 2; - } - else if (aLinkMargin > 2) - { - return 1; - } - else - { - return 0; - } -} - ThreadError Mle::HandleParentResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint32_t aKeySequence) { ThreadError error = kThreadError_None; + const ThreadMessageInfo *threadMessageInfo = reinterpret_cast(aMessageInfo.mLinkInfo); ResponseTlv response; SourceAddressTlv sourceAddress; LeaderDataTlv leaderData; @@ -1649,14 +1630,14 @@ ThreadError Mle::HandleParentResponse(const Message &aMessage, const Ip6::Messag SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kLinkMargin, sizeof(linkMarginTlv), linkMarginTlv)); VerifyOrExit(linkMarginTlv.IsValid(), error = kThreadError_Parse); - linkMargin = reinterpret_cast(aMessageInfo.mLinkInfo)->mLinkMargin; + linkMargin = LinkQualityInfo::ConvertRssToLinkMargin(threadMessageInfo->mRss); if (linkMargin > linkMarginTlv.GetLinkMargin()) { linkMargin = linkMarginTlv.GetLinkMargin(); } - link_quality = LinkMarginToQuality(linkMargin); + link_quality = LinkQualityInfo::ConvertLinkMarginToLinkQuality(linkMargin); VerifyOrExit(mParentRequestState != kParentRequestRouter || link_quality == 3, ;); @@ -1708,6 +1689,8 @@ ThreadError Mle::HandleParentResponse(const Message &aMessage, const Ip6::Messag mParent.mValid.mLinkFrameCounter = linkFrameCounter.GetFrameCounter(); mParent.mValid.mMleFrameCounter = mleFrameCounter.GetFrameCounter(); mParent.mMode = ModeTlv::kModeFFD | ModeTlv::kModeRxOnWhenIdle | ModeTlv::kModeFullNetworkData; + mParent.mLinkInfo.Clear(); + mParent.mLinkInfo.AddRss(threadMessageInfo->mRss); mParent.mState = Neighbor::kStateValid; assert(aKeySequence == mKeyManager.GetCurrentKeySequence() || aKeySequence == mKeyManager.GetPreviousKeySequence()); diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index a5c3a8287..ed6227ea0 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -805,16 +805,6 @@ protected: */ Mac::ShortAddress GetNextHop(uint16_t aDestination) const; - /** - * This method converts a link margin value to a link quality value. - * - * @param[in] aLinkMargin The Link Margin in dB. - * - * @returns The link quality value. - * - */ - uint8_t LinkMarginToQuality(uint8_t aLinkMargin); - /** * This method generates an MLE Data Request message. * diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index 4a7d8b0a7..8fc0d305b 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -615,8 +615,12 @@ ThreadError MleRouter::HandleLinkRequest(const Message &aMessage, const Ip6::Mes if (neighbor->mState != Neighbor::kStateValid) { + const ThreadMessageInfo *threadMessageInfo = + reinterpret_cast(aMessageInfo.mLinkInfo); + memcpy(&neighbor->mMacAddr, &macAddr, sizeof(neighbor->mMacAddr)); - neighbor->mRssi = reinterpret_cast(aMessageInfo.mLinkInfo)->mLinkMargin; + neighbor->mLinkInfo.Clear(); + neighbor->mLinkInfo.AddRss(threadMessageInfo->mRss); neighbor->mState = Neighbor::kStateLinkRequest; } else @@ -651,6 +655,7 @@ ThreadError MleRouter::SendLinkAccept(const Ip6::MessageInfo &aMessageInfo, Neig const TlvRequestTlv &aTlvRequest, const ChallengeTlv &aChallenge) { ThreadError error = kThreadError_None; + const ThreadMessageInfo *threadMessageInfo = reinterpret_cast(aMessageInfo.mLinkInfo); static const uint8_t routerTlvs[] = {Tlv::kLinkMargin}; Message *message; Header::Command command; @@ -669,7 +674,7 @@ ThreadError MleRouter::SendLinkAccept(const Ip6::MessageInfo &aMessageInfo, Neig SuccessOrExit(error = AppendMleFrameCounter(*message)); // always append a link margin, regardless of whether or not it was requested - linkMargin = reinterpret_cast(aMessageInfo.mLinkInfo)->mLinkMargin; + linkMargin = LinkQualityInfo::ConvertRssToLinkMargin(threadMessageInfo->mRss); SuccessOrExit(error = AppendLinkMargin(*message, linkMargin)); if (aNeighbor != NULL && GetChildId(aNeighbor->mValid.mRloc16) == 0) @@ -748,6 +753,7 @@ ThreadError MleRouter::HandleLinkAccept(const Message &aMessage, const Ip6::Mess uint32_t aKeySequence, bool aRequest) { ThreadError error = kThreadError_None; + const ThreadMessageInfo *threadMessageInfo = reinterpret_cast(aMessageInfo.mLinkInfo); Neighbor *neighbor = NULL; Mac::ExtAddress macAddr; VersionTlv version; @@ -860,9 +866,8 @@ ThreadError MleRouter::HandleLinkAccept(const Message &aMessage, const Ip6::Mess case kDeviceStateChild: SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kLinkMargin, sizeof(linkMargin), linkMargin)); VerifyOrExit(linkMargin.IsValid(), error = kThreadError_Parse); - mRouters[routerId].mLinkQualityOut = LinkMarginToQuality(linkMargin.GetLinkMargin()); - mRouters[routerId].mLinkQualityIn = LinkMarginToQuality(reinterpret_cast - (aMessageInfo.mLinkInfo)->mLinkMargin); + mRouters[routerId].mLinkQualityOut = + LinkQualityInfo::ConvertLinkMarginToLinkQuality(linkMargin.GetLinkMargin()); break; case kDeviceStateRouter: @@ -875,9 +880,8 @@ ThreadError MleRouter::HandleLinkAccept(const Message &aMessage, const Ip6::Mess // Link Margin SuccessOrExit(error = Tlv::GetTlv(aMessage, Tlv::kLinkMargin, sizeof(linkMargin), linkMargin)); VerifyOrExit(linkMargin.IsValid(), error = kThreadError_Parse); - mRouters[routerId].mLinkQualityOut = LinkMarginToQuality(linkMargin.GetLinkMargin()); - mRouters[routerId].mLinkQualityIn = LinkMarginToQuality(reinterpret_cast - (aMessageInfo.mLinkInfo)->mLinkMargin); + mRouters[routerId].mLinkQualityOut = + LinkQualityInfo::ConvertLinkMarginToLinkQuality(linkMargin.GetLinkMargin()); // update routing table if (routerId != mRouterId && mRouters[routerId].mNextHop == kMaxRouterId) @@ -896,6 +900,8 @@ ThreadError MleRouter::HandleLinkAccept(const Message &aMessage, const Ip6::Mess neighbor->mValid.mMleFrameCounter = mleFrameCounter.GetFrameCounter(); neighbor->mLastHeard = Timer::GetNow(); neighbor->mMode = ModeTlv::kModeFFD | ModeTlv::kModeRxOnWhenIdle | ModeTlv::kModeFullNetworkData; + neighbor->mLinkInfo.Clear(); + neighbor->mLinkInfo.AddRss(threadMessageInfo->mRss); neighbor->mState = Neighbor::kStateValid; assert(aKeySequence == mKeyManager.GetCurrentKeySequence() || aKeySequence == mKeyManager.GetPreviousKeySequence()); @@ -1018,7 +1024,7 @@ uint8_t MleRouter::GetLinkCost(uint8_t aRouterId) mRouters[aRouterId].mState == Neighbor::kStateValid, rval = kMaxRouteCost); - rval = mRouters[aRouterId].mLinkQualityIn; + rval = mRouters[aRouterId].mLinkInfo.GetLinkQuality(); if (rval > mRouters[aRouterId].mLinkQualityOut) { @@ -1070,6 +1076,7 @@ exit: ThreadError MleRouter::HandleAdvertisement(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { ThreadError error = kThreadError_None; + const ThreadMessageInfo *threadMessageInfo = reinterpret_cast(aMessageInfo.mLinkInfo); Mac::ExtAddress macAddr; SourceAddressTlv sourceAddress; LeaderDataTlv leaderData; @@ -1171,6 +1178,8 @@ ThreadError MleRouter::HandleAdvertisement(const Message &aMessage, const Ip6::M if (router->mState != Neighbor::kStateValid) { memcpy(&router->mMacAddr, &macAddr, sizeof(router->mMacAddr)); + router->mLinkInfo.Clear(); + router->mLinkInfo.AddRss(threadMessageInfo->mRss); router->mState = Neighbor::kStateLinkRequest; router->mPreviousKey = false; SendLinkRequest(router); @@ -1179,8 +1188,6 @@ ThreadError MleRouter::HandleAdvertisement(const Message &aMessage, const Ip6::M } router->mLastHeard = Timer::GetNow(); - router->mLinkQualityIn = - LinkMarginToQuality(reinterpret_cast(aMessageInfo.mLinkInfo)->mLinkMargin); ExitNow(); @@ -1198,6 +1205,8 @@ ThreadError MleRouter::HandleAdvertisement(const Message &aMessage, const Ip6::M if (router->mState != Neighbor::kStateValid) { memcpy(&router->mMacAddr, &macAddr, sizeof(router->mMacAddr)); + router->mLinkInfo.Clear(); + router->mLinkInfo.AddRss(threadMessageInfo->mRss); router->mState = Neighbor::kStateLinkRequest; router->mDataRequest = false; router->mPreviousKey = false; @@ -1206,8 +1215,6 @@ ThreadError MleRouter::HandleAdvertisement(const Message &aMessage, const Ip6::M } router->mLastHeard = Timer::GetNow(); - router->mLinkQualityIn = - LinkMarginToQuality(reinterpret_cast(aMessageInfo.mLinkInfo)->mLinkMargin); break; } @@ -1335,7 +1342,8 @@ void MleRouter::UpdateRoutes(const RouteTlv &aRoute, uint8_t aRouterId) } otLogDebgMle("%x: %x %d %d %d %d\n", GetRloc16(i), GetRloc16(mRouters[i].mNextHop), - mRouters[i].mCost, GetLinkCost(i), mRouters[i].mLinkQualityIn, mRouters[i].mLinkQualityOut); + mRouters[i].mCost, GetLinkCost(i), mRouters[i].mLinkInfo.GetLinkQuality(), + mRouters[i].mLinkQualityOut); } #endif @@ -1344,6 +1352,7 @@ void MleRouter::UpdateRoutes(const RouteTlv &aRoute, uint8_t aRouterId) ThreadError MleRouter::HandleParentRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { ThreadError error = kThreadError_None; + const ThreadMessageInfo *threadMessageInfo = reinterpret_cast(aMessageInfo.mLinkInfo); Mac::ExtAddress macAddr; VersionTlv version; ScanMaskTlv scanMask; @@ -1401,11 +1410,12 @@ ThreadError MleRouter::HandleParentRequest(const Message &aMessage, const Ip6::M // MAC Address memcpy(&child->mMacAddr, &macAddr, sizeof(child->mMacAddr)); - + child->mLinkInfo.Clear(); + child->mLinkInfo.AddRss(threadMessageInfo->mRss); child->mState = Neighbor::kStateParentRequest; child->mDataRequest = false; child->mPreviousKey = false; - child->mRssi = reinterpret_cast(aMessageInfo.mLinkInfo)->mLinkMargin; + child->mTimeout = Timer::SecToMsec(2 * kParentRequestChildTimeout); SuccessOrExit(error = SendParentResponse(child, challenge)); @@ -1479,8 +1489,8 @@ void MleRouter::HandleStateUpdateTimer(void) if ((Timer::GetNow() - mRouters[i].mLastHeard) >= Timer::SecToMsec(kMaxNeighborAge)) { mRouters[i].mState = Neighbor::kStateInvalid; + mRouters[i].mLinkInfo.Clear(); mRouters[i].mNextHop = kMaxRouterId; - mRouters[i].mLinkQualityIn = 0; mRouters[i].mLinkQualityOut = 0; mRouters[i].mLastHeard = Timer::GetNow(); } @@ -1534,7 +1544,7 @@ ThreadError MleRouter::SendParentResponse(Child *aChild, const ChallengeTlv &cha } SuccessOrExit(error = AppendChallenge(*message, aChild->mPending.mChallenge, sizeof(aChild->mPending.mChallenge))); - SuccessOrExit(error = AppendLinkMargin(*message, aChild->mRssi)); + SuccessOrExit(error = AppendLinkMargin(*message, aChild->mLinkInfo.GetLinkMargin())); SuccessOrExit(error = AppendConnectivity(*message)); SuccessOrExit(error = AppendVersion(*message)); @@ -1589,6 +1599,7 @@ ThreadError MleRouter::HandleChildIdRequest(const Message &aMessage, const Ip6:: uint32_t aKeySequence) { ThreadError error = kThreadError_None; + const ThreadMessageInfo *threadMessageInfo = reinterpret_cast(aMessageInfo.mLinkInfo); Mac::ExtAddress macAddr; ResponseTlv response; LinkFrameCounterTlv linkFrameCounter; @@ -1664,6 +1675,7 @@ ThreadError MleRouter::HandleChildIdRequest(const Message &aMessage, const Ip6:: child->mValid.mLinkFrameCounter = linkFrameCounter.GetFrameCounter(); child->mValid.mMleFrameCounter = mleFrameCounter.GetFrameCounter(); child->mMode = mode.GetMode(); + child->mLinkInfo.AddRss(threadMessageInfo->mRss); child->mTimeout = timeout.GetTimeout(); if (mode.GetMode() & ModeTlv::kModeFullNetworkData) @@ -2713,7 +2725,7 @@ ThreadError MleRouter::AppendConnectivity(Message &aMessage) break; case kDeviceStateChild: - switch (mParent.mLinkQualityIn) + switch (mParent.mLinkInfo.GetLinkQuality()) { case 1: tlv.SetLinkQuality1(tlv.GetLinkQuality1() + 1); @@ -2728,7 +2740,7 @@ ThreadError MleRouter::AppendConnectivity(Message &aMessage) break; } - cost += LqiToCost(mParent.mLinkQualityIn); + cost += LqiToCost(mParent.mLinkInfo.GetLinkQuality()); break; case kDeviceStateRouter: @@ -2747,7 +2759,7 @@ ThreadError MleRouter::AppendConnectivity(Message &aMessage) continue; } - lqi = mRouters[i].mLinkQualityIn; + lqi = mRouters[i].mLinkInfo.GetLinkQuality(); if (lqi > mRouters[i].mLinkQualityOut) { @@ -2865,7 +2877,7 @@ ThreadError MleRouter::AppendRoute(Message &aMessage) } tlv.SetRouteCost(routeCount, cost); - tlv.SetLinkQualityIn(routeCount, mRouters[i].mLinkQualityIn); + tlv.SetLinkQualityIn(routeCount, mRouters[i].mLinkInfo.GetLinkQuality()); tlv.SetLinkQualityOut(routeCount, mRouters[i].mLinkQualityOut); } diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp index ed86c3888..34ecb33dc 100644 --- a/src/core/thread/thread_netif.hpp +++ b/src/core/thread/thread_netif.hpp @@ -225,7 +225,7 @@ private: */ struct ThreadMessageInfo { - uint8_t mLinkMargin; ///< The Link Margin for a received message in dB. + int8_t mRss; ///< The Received Signal Strength in dBm. uint8_t mLqi; ///< The Link Quality Indicator for a received message. bool mLinkSecurity; ///< Indicates whether or not link security is enabled. }; diff --git a/src/core/thread/topology.hpp b/src/core/thread/topology.hpp index ba04ffed3..44b835fee 100644 --- a/src/core/thread/topology.hpp +++ b/src/core/thread/topology.hpp @@ -38,6 +38,7 @@ #include #include #include +#include namespace Thread { @@ -77,11 +78,11 @@ public: kStateLinkRequest, ///< Sent a MLE Link Request message kStateValid, ///< Link is valid }; - State mState : 3; ///< The link state - uint8_t mMode : 4; ///< The MLE device mode - bool mPreviousKey : 1; ///< Indicates whether or not the neighbor is still using a previous key - bool mDataRequest : 1; ///< Indicates whether or not a Data Poll was received - int8_t mRssi; ///< Received Signal Strength Indicator + State mState : 3; ///< The link state + uint8_t mMode : 4; ///< The MLE device mode + bool mPreviousKey : 1; ///< Indicates whether or not the neighbor is still using a previous key + bool mDataRequest : 1; ///< Indicates whether or not a Data Poll was received + LinkQualityInfo mLinkInfo; ///< Link quality info (contains average RSS, link margin and link quality). }; /** @@ -111,7 +112,6 @@ class Router : public Neighbor public: uint8_t mNextHop; ///< The next hop towards this router uint8_t mLinkQualityOut : 2; ///< The link quality out for this router - uint8_t mLinkQualityIn : 2; ///< The link quality in for this router uint8_t mCost : 4; ///< The cost to this router bool mAllocated : 1; ///< Indicates whether or not this entry is allocated bool mReclaimDelay : 1; ///< Indicates whether or not this entry is waiting to be reclaimed diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index 7a454522b..0a3e5a424 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -68,6 +68,7 @@ check_PROGRAMS = \ test-aes \ test-hmac-sha256 \ test-lowpan \ + test-link-quality \ test-mac-frame \ test-message \ test-timer \ @@ -95,6 +96,9 @@ test_aes_SOURCES = test_aes.cpp test_hmac_sha256_LDADD = $(COMMON_LDADD) test_hmac_sha256_SOURCES = test_hmac_sha256.cpp +test_link_quality_LDADD = $(COMMON_LDADD) +test_link_quality_SOURCES = test_link_quality.cpp + test_lowpan_LDADD = $(COMMON_LDADD) test_lowpan_SOURCES = test_lowpan.cpp test_util.cpp test_vector.c diff --git a/tests/unit/test_link_quality.cpp b/tests/unit/test_link_quality.cpp new file mode 100644 index 000000000..764830ca7 --- /dev/null +++ b/tests/unit/test_link_quality.cpp @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2016, Nest Labs, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_util.h" +#include +#include + +#include + +namespace Thread { + +extern"C" void otSignalTaskletPending(void) +{ +} + +enum +{ + kMaxRssValue = 0, + kMinRssValue = -128, + + kStringBuffferSize = 80, + + kRssAverageMaxDiff = 16, + kNumRssAdds = 300, + + kEncodedAverageBitShift = 3, + kEncodedAverageMultiple = (1 << kEncodedAverageBitShift), + kEncodedAverageBitMask = (1 << kEncodedAverageBitShift) - 1, +}; + +#define MIN_RSS(_rss1, _rss2) (((_rss1) < (_rss2)) ? (_rss1) : (_rss2)) +#define MAX_RSS(_rss1, _rss2) (((_rss1) < (_rss2)) ? (_rss2) : (_rss1)) +#define ABS(value) (((value) >= 0) ? (value) : -(value)) + +// This struct contains RSS values and test data for checking link quality info calss. +struct RssTestData +{ + const int8_t *mRssList; // Array of RSS values. + size_t mRssListSize; // Size of RSS list. + uint8_t mExpectedLinkQuality; // Expected final link quality value. +}; + +// Checks the encoded average RSS value to match the value from GetAverageRss(). +void VerifyEncodedRssValue(LinkQualityInfo &aLinkInfo) +{ + int8_t rss = aLinkInfo.GetAverageRss(); + uint16_t encodedRss = aLinkInfo.GetAverageRssAsEncodedWord(); + + if (rss != LinkQualityInfo::kUnknownRss) + { + VerifyOrQuit(rss == -static_cast((encodedRss + (kEncodedAverageMultiple / 2)) >> kEncodedAverageBitShift), + "TestLinkQualityInfo failed - Ecoded RSS does not match the value from GetAverageRss()."); + } + else + { + VerifyOrQuit(encodedRss == 0, + "TestLinkQualityInfo failed - Ecoded RSS does not match the value from GetAverageRss()."); + } +} + +// This function prints the values in the passed in link info instance. It is invoked as the final step in test-case. +void PrintOutcome(LinkQualityInfo &aLinkInfo) +{ + char stringBuf[kStringBuffferSize]; + + SuccessOrQuit(aLinkInfo.GetAverageRssAsString(stringBuf, sizeof(stringBuf)), + "TestLinkQualityInfo failed - GetAverageRssAsString() failed."); + + printf("AveRss = %-4d, \"%-14s\", ", aLinkInfo.GetAverageRss(), stringBuf); + printf("LinkMargin = %-4d, LinkQuality = %d", aLinkInfo.GetLinkMargin(), aLinkInfo.GetLinkQuality()); + + // This test-case succeeded. + printf(" -> PASS\n"); +} + +void TestLinkQualityData(RssTestData anRssData) +{ + LinkQualityInfo linkInfo; + int8_t rss, ave, min, max; + size_t i; + + printf("- - - - - - - - - - - - - - - - - -\n"); + min = kMinRssValue; + max = kMaxRssValue; + + for (i = 0; i < anRssData.mRssListSize; i++) + { + rss = anRssData.mRssList[i]; + min = MIN_RSS(rss, min); + max = MAX_RSS(rss, max); + linkInfo.AddRss(rss); + ave = linkInfo.GetAverageRss(); + VerifyOrQuit(ave >= min, + "TestLinkQualityInfo failed - GetAverageRss() is smaller than min value."); + VerifyOrQuit(ave <= max, + "TestLinkQualityInfo failed - GetAverageRss() is larger than min value"); + VerifyEncodedRssValue(linkInfo); + printf("%02u) AddRss(%4d): ", (unsigned int)i, rss); + PrintOutcome(linkInfo); + } + + VerifyOrQuit(linkInfo.GetLinkQuality() == anRssData.mExpectedLinkQuality, + "TestLinkQualityInfo failed - GetLinkQuality() is incorrect"); +} + +void TestRssAveraging(void) +{ + LinkQualityInfo linkInfo; + int8_t rss, rss2, ave; + int16_t diff; + size_t i, j, k; + const int8_t rssValues[] = { kMinRssValue, -70, -40, -41, -10, kMaxRssValue}; + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Values after initialization. + + printf("\nAfter Initialization: "); + VerifyOrQuit(linkInfo.GetAverageRss() == LinkQualityInfo::kUnknownRss, + "TestLinkQualityInfo failed - Inital value from GetAverageRss() is incorrect."); + VerifyOrQuit(linkInfo.GetLinkMargin() == 0, + "TestLinkQualityInfo failed - Inital value for link margin is incorrect."); + VerifyEncodedRssValue(linkInfo); + PrintOutcome(linkInfo); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Adding a single value + rss = -70; + printf("AddRss(%d): ", rss); + linkInfo.AddRss(rss); + VerifyOrQuit(linkInfo.GetAverageRss() == rss, + "TestLinkQualityInfo - GetAverageRss() failed after a single AddRss()."); + VerifyEncodedRssValue(linkInfo); + PrintOutcome(linkInfo); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Clear + + printf("Clear(): "); + linkInfo.Clear(); + VerifyOrQuit(linkInfo.GetAverageRss() == LinkQualityInfo::kUnknownRss, + "TestLinkQualityInfo failed - GetAverageRss() after Clear() is incorrect."); + VerifyOrQuit(linkInfo.GetLinkMargin() == 0, + "TestLinkQualityInfo failed - link margin value after Clear() is incorrect."); + VerifyEncodedRssValue(linkInfo); + PrintOutcome(linkInfo); + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Adding the same value many times. + + printf("- - - - - - - - - - - - - - - - - -\n"); + + for (j = 0; j < sizeof(rssValues); j++) + { + linkInfo.Clear(); + rss = rssValues[j]; + printf("AddRss(%4d) %d times: ", rss, kNumRssAdds); + + for (i = 0; i < kNumRssAdds; i++) + { + linkInfo.AddRss(rss); + VerifyOrQuit(linkInfo.GetAverageRss() == rss, + "TestLinkQualityInfo failed - GetAverageRss() returned incorrect value."); + VerifyEncodedRssValue(linkInfo); + } + + PrintOutcome(linkInfo); + + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Adding two RSS values: + + printf("- - - - - - - - - - - - - - - - - -\n"); + + for (j = 0; j < sizeof(rssValues); j++) + { + rss = rssValues[j]; + + for (k = 0; k < sizeof(rssValues); k++) + { + if (k == j) + { + continue; + } + + rss2 = rssValues[k]; + linkInfo.Clear(); + linkInfo.AddRss(rss); + linkInfo.AddRss(rss2); + printf("AddRss(%4d), AddRss(%4d): ", rss, rss2); + VerifyOrQuit(linkInfo.GetAverageRss() == ((rss + rss2) >> 1), + "TestLinkQualityInfo failed - GetAverageRss() returned incorrect value."); + VerifyEncodedRssValue(linkInfo); + PrintOutcome(linkInfo); + } + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Adding one value many times and a different value once: + + printf("- - - - - - - - - - - - - - - - - -\n"); + + for (j = 0; j < sizeof(rssValues); j++) + { + rss = rssValues[j]; + + for (k = 0; k < sizeof(rssValues); k++) + { + if (k == j) + { + continue; + } + + rss2 = rssValues[k]; + linkInfo.Clear(); + + for (i = 0; i < kNumRssAdds; i++) + { + linkInfo.AddRss(rss); + } + + linkInfo.AddRss(rss2); + printf("AddRss(%4d) %d times, AddRss(%4d): ", rss, kNumRssAdds, rss2); + ave = linkInfo.GetAverageRss(); + VerifyOrQuit(ave >= MIN_RSS(rss, rss2), + "TestLinkQualityInfo failed - GetAverageRss() returned incorrect value."); + VerifyOrQuit(ave <= MAX_RSS(rss, rss2), + "TestLinkQualityInfo failed - GetAverageRss() returned incorrect value."); + VerifyEncodedRssValue(linkInfo); + PrintOutcome(linkInfo); + } + } + + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Adding two alteraing values many times: + + printf("- - - - - - - - - - - - - - - - - -\n"); + + for (j = 0; j < sizeof(rssValues); j++) + { + rss = rssValues[j]; + + for (k = 0; k < sizeof(rssValues); k++) + { + if (k == j) + { + continue; + } + + rss2 = rssValues[k]; + linkInfo.Clear(); + + for (i = 0; i < kNumRssAdds; i++) + { + linkInfo.AddRss(rss); + linkInfo.AddRss(rss2); + ave = linkInfo.GetAverageRss(); + VerifyOrQuit(ave >= MIN_RSS(rss, rss2), + "TestLinkQualityInfo failed - GetAverageRss() is smaller than min value."); + VerifyOrQuit(ave <= MAX_RSS(rss, rss2), + "TestLinkQualityInfo failed - GetAverageRss() is larger than min value."); + diff = ave; + diff -= (rss + rss2) >> 1; + VerifyOrQuit(ABS(diff) <= kRssAverageMaxDiff, + "TestLinkQualityInfo failed - GetAverageRss() is incorrect"); + VerifyEncodedRssValue(linkInfo); + } + + printf("[AddRss(%4d), AddRss(%4d)] %d times: ", rss, rss2, kNumRssAdds); + PrintOutcome(linkInfo); + } + } +} + +void TestLinkQualityCalculations(void) +{ + const int8_t rssList1[] = { -81, -80, -79, -78, -76, -80, -77, -75, -77, -76, -77, -74}; + const RssTestData rssData1 = + { + rssList1, // mRssList + sizeof(rssList1), // mRssListSize + 3 // mExpectedLinkQuality + }; + + const int8_t rssList2[] = { -90, -80, -85 }; + const RssTestData rssData2 = + { + rssList2, // mRssList + sizeof(rssList2), // mRssListSize + 2 // mExpectedLinkQuality + }; + + const int8_t rssList3[] = { -95, -96, -98, -99, -100, -100, -98, -99, -100, -100, -100, -100, -100 }; + const RssTestData rssData3 = + { + rssList3, // mRssList + sizeof(rssList3), // mRssListSize + 0 // mExpectedLinkQuality + }; + + const int8_t rssList4[] = { -75, -100, -100, -100, -100, -100, -95, -92, -93, -94, -93, -93 }; + const RssTestData rssData4 = + { + rssList4, // mRssList + sizeof(rssList4), // mRssListSize + 1 // mExpectedLinkQuality + }; + + TestLinkQualityData(rssData1); + TestLinkQualityData(rssData2); + TestLinkQualityData(rssData3); + TestLinkQualityData(rssData4); +} + +} // namespace Thread + +int main(void) +{ + Thread::TestRssAveraging(); + Thread::TestLinkQualityCalculations(); + printf("All tests passed\n"); + return 0; +}