blob: 7bcdbc6ba8057c309e18f3b7ca5e1cc723c6b9ea [file] [log] [blame]
/*
* Copyright (c) 2016, The OpenThread Authors.
* 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 <openthread/config.h>
#include "link_quality.hpp"
#include <stdio.h>
#include "utils/wrap_string.h"
#include "common/code_utils.hpp"
namespace ot {
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";
//-------------------------------------------------------------------------------
LinkQualityInfo::LinkQualityInfo(void)
{
Clear();
}
void LinkQualityInfo::Clear(void)
{
mRssAverage = 0;
mCount = 0;
mLinkQuality = 0;
mLastRss = 0;
}
void LinkQualityInfo::AddRss(int8_t aNoiseFloor, int8_t aRss)
{
uint16_t newValue;
uint16_t oldAverage;
VerifyOrExit(aRss != OT_RADIO_RSSI_INVALID);
mLastRss = aRss;
// Restrict/Cap the RSS value to the closed range [0, -128] so the value can fit in 8 bits.
if (aRss > 0)
{
aRss = 0;
}
// Multiply the the RSS value by a precision multiple (currently -8).
newValue = static_cast<uint16_t>(-aRss);
newValue <<= kRssAveragePrecisionMultipleBitShift;
oldAverage = mRssAverage;
if (mCount >= kRssCountForWeightCoefficientOneEighth)
{
// New average = old average * 7/8 + new value * 1/8
mRssAverage = static_cast<uint16_t>(((oldAverage << 3) - oldAverage + newValue) >> 3);
}
else if (mCount >= kRssCountForWeightCoefficientOneFourth)
{
// New average = old average * 3/4 + new value * 1/4
mRssAverage = static_cast<uint16_t>(((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(aNoiseFloor);
exit:
return;
}
int8_t LinkQualityInfo::GetAverageRss(void) const
{
int8_t average = OT_RADIO_RSSI_INVALID;
if (mCount != 0)
{
average = -static_cast<int8_t>(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;
}
otError LinkQualityInfo::GetAverageRssAsString(char *aCharBuffer, size_t aBufferLen) const
{
otError error = OT_ERROR_NONE;
int charsWritten = 0;
if (mCount == 0)
{
charsWritten = static_cast<int>(strlcpy(aCharBuffer, kUnknownRssString, aBufferLen));
}
else
{
charsWritten = snprintf(aCharBuffer, aBufferLen, "%d.%s dBm",
-(mRssAverage >> kRssAveragePrecisionMultipleBitShift),
kLinkQualityDecimalDigitsString[mRssAverage & kRssAveragePrecisionMultipleBitMask]);
}
VerifyOrExit(charsWritten >= 0, error = OT_ERROR_NO_BUFS);
VerifyOrExit(charsWritten < static_cast<int>(aBufferLen), error = OT_ERROR_NO_BUFS);
exit:
return error;
}
uint8_t LinkQualityInfo::GetLinkMargin(int8_t aNoiseFloor) const
{
return ConvertRssToLinkMargin(aNoiseFloor, GetAverageRss());
}
uint8_t LinkQualityInfo::GetLinkQuality(int8_t aNoiseFloor)
{
UpdateLinkQuality(aNoiseFloor);
return mLinkQuality;
}
int8_t LinkQualityInfo::GetLastRss(void) const
{
return mLastRss;
}
void LinkQualityInfo::UpdateLinkQuality(int8_t aNoiseFloor)
{
if (mCount != 0)
{
mLinkQuality = CalculateLinkQuality(GetLinkMargin(aNoiseFloor), mLinkQuality);
}
else
{
mLinkQuality = CalculateLinkQuality(GetLinkMargin(aNoiseFloor), kNoLastLinkQualityValue);
}
}
uint8_t LinkQualityInfo::ConvertRssToLinkMargin(int8_t aNoiseFloor, int8_t aRss)
{
int8_t linkMargin = aRss - aNoiseFloor;
if (linkMargin < 0 || aRss == OT_RADIO_RSSI_INVALID)
{
linkMargin = 0;
}
return static_cast<uint8_t>(linkMargin);
}
uint8_t LinkQualityInfo::ConvertLinkMarginToLinkQuality(uint8_t aLinkMargin)
{
return CalculateLinkQuality(aLinkMargin, kNoLastLinkQualityValue);
}
uint8_t LinkQualityInfo::ConvertRssToLinkQuality(int8_t aNoiseFloor, int8_t aRss)
{
return ConvertLinkMarginToLinkQuality(ConvertRssToLinkMargin(aNoiseFloor, aRss));
}
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;
}
} // namespace ot