1 /*
2  *  Copyright (c) 2016, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file implements link quality information processing and storage.
32  */
33 
34 #include "link_quality.hpp"
35 
36 #include "instance/instance.hpp"
37 
38 namespace ot {
39 
40 // This array gives the decimal point digits representing 0/8, 1/8, ..., 7/8 (does not include the '.').
41 static const char *const kDigitsString[8] = {
42     // 0/8,  1/8,   2/8,   3/8,   4/8,   5/8,   6/8,   7/8
43     "0", "125", "25", "375", "5", "625", "75", "875"};
44 
AddSample(bool aSuccess,uint16_t aWeight)45 void SuccessRateTracker::AddSample(bool aSuccess, uint16_t aWeight)
46 {
47     uint32_t oldAverage = mFailureRate;
48     uint32_t newValue   = (aSuccess) ? 0 : kMaxRateValue;
49     uint32_t n          = aWeight;
50 
51     // `n/2` is added to the sum to ensure rounding the value to the nearest integer when dividing by `n`
52     // (e.g., 1.2 -> 1, 3.5 -> 4).
53 
54     mFailureRate = static_cast<uint16_t>(((oldAverage * (n - 1)) + newValue + (n / 2)) / n);
55 }
56 
Add(int8_t aRss)57 Error RssAverager::Add(int8_t aRss)
58 {
59     Error    error = kErrorNone;
60     uint16_t newValue;
61 
62     VerifyOrExit(aRss != Radio::kInvalidRssi, error = kErrorInvalidArgs);
63 
64     // Restrict the RSS value to the closed range [-128, 0]
65     // so the RSS times precision multiple can fit in 11 bits.
66     aRss = Min<int8_t>(aRss, 0);
67 
68     // Multiply the RSS value by a precision multiple (currently -8).
69 
70     newValue = static_cast<uint16_t>(-aRss);
71     newValue <<= kPrecisionBitShift;
72 
73     mCount += (mCount < (1 << kCoeffBitShift));
74     // Maintain arithmetic mean.
75     // newAverage = newValue * (1/mCount) + oldAverage * ((mCount -1)/mCount)
76     mAverage = static_cast<uint16_t>(((mAverage * (mCount - 1)) + newValue) / mCount);
77 
78 exit:
79     return error;
80 }
81 
GetAverage(void) const82 int8_t RssAverager::GetAverage(void) const
83 {
84     int8_t average;
85 
86     VerifyOrExit(mCount != 0, average = Radio::kInvalidRssi);
87 
88     average = -static_cast<int8_t>(mAverage >> kPrecisionBitShift);
89 
90     // Check for possible round up (e.g., average of -71.5 --> -72)
91 
92     if ((mAverage & kPrecisionBitMask) >= (kPrecision >> 1))
93     {
94         average--;
95     }
96 
97 exit:
98     return average;
99 }
100 
ToString(void) const101 RssAverager::InfoString RssAverager::ToString(void) const
102 {
103     InfoString string;
104 
105     VerifyOrExit(mCount != 0);
106     string.Append("%d.%s", -(mAverage >> kPrecisionBitShift), kDigitsString[mAverage & kPrecisionBitMask]);
107 
108 exit:
109     return string;
110 }
111 
Add(uint8_t aLqi)112 void LqiAverager::Add(uint8_t aLqi)
113 {
114     uint8_t  count;
115     uint16_t newAverage;
116 
117     if (mCount < NumericLimits<uint8_t>::kMax)
118     {
119         mCount++;
120     }
121 
122     count = Min(static_cast<uint8_t>(1 << kCoeffBitShift), mCount);
123 
124     newAverage = mAverage;
125     newAverage = (newAverage * (count - 1) + aLqi) / count;
126 
127     mAverage = static_cast<uint8_t>(newAverage);
128 }
129 
Clear(void)130 void LinkQualityInfo::Clear(void)
131 {
132     mRssAverager.Clear();
133     SetLinkQuality(kLinkQuality0);
134     mLastRss = Radio::kInvalidRssi;
135 
136     mFrameErrorRate.Clear();
137     mMessageErrorRate.Clear();
138 }
139 
AddRss(int8_t aRss)140 void LinkQualityInfo::AddRss(int8_t aRss)
141 {
142     uint8_t oldLinkQuality = kNoLinkQuality;
143 
144     VerifyOrExit(aRss != Radio::kInvalidRssi);
145 
146     mLastRss = aRss;
147 
148     if (mRssAverager.HasAverage())
149     {
150         oldLinkQuality = GetLinkQuality();
151     }
152 
153     SuccessOrExit(mRssAverager.Add(aRss));
154 
155     SetLinkQuality(CalculateLinkQuality(GetLinkMargin(), oldLinkQuality));
156 
157 exit:
158     return;
159 }
160 
GetLinkMargin(void) const161 uint8_t LinkQualityInfo::GetLinkMargin(void) const
162 {
163     return ComputeLinkMargin(Get<Mac::SubMac>().GetNoiseFloor(), GetAverageRss());
164 }
165 
ToInfoString(void) const166 LinkQualityInfo::InfoString LinkQualityInfo::ToInfoString(void) const
167 {
168     InfoString string;
169 
170     string.Append("aveRss:%s, lastRss:%d, linkQuality:%d", mRssAverager.ToString().AsCString(), GetLastRss(),
171                   GetLinkQuality());
172 
173     return string;
174 }
175 
ComputeLinkMargin(int8_t aNoiseFloor,int8_t aRss)176 uint8_t ComputeLinkMargin(int8_t aNoiseFloor, int8_t aRss)
177 {
178     int8_t linkMargin = aRss - aNoiseFloor;
179 
180     if (linkMargin < 0 || aRss == Radio::kInvalidRssi)
181     {
182         linkMargin = 0;
183     }
184 
185     return static_cast<uint8_t>(linkMargin);
186 }
187 
LinkQualityForLinkMargin(uint8_t aLinkMargin)188 LinkQuality LinkQualityForLinkMargin(uint8_t aLinkMargin)
189 {
190     return LinkQualityInfo::CalculateLinkQuality(aLinkMargin, LinkQualityInfo::kNoLinkQuality);
191 }
192 
GetTypicalRssForLinkQuality(int8_t aNoiseFloor,LinkQuality aLinkQuality)193 int8_t GetTypicalRssForLinkQuality(int8_t aNoiseFloor, LinkQuality aLinkQuality)
194 {
195     int8_t linkMargin = 0;
196 
197     switch (aLinkQuality)
198     {
199     case kLinkQuality3:
200         linkMargin = LinkQualityInfo::kLinkQuality3LinkMargin;
201         break;
202 
203     case kLinkQuality2:
204         linkMargin = LinkQualityInfo::kLinkQuality2LinkMargin;
205         break;
206 
207     case kLinkQuality1:
208         linkMargin = LinkQualityInfo::kLinkQuality1LinkMargin;
209         break;
210 
211     default:
212         linkMargin = LinkQualityInfo::kLinkQuality0LinkMargin;
213         break;
214     }
215 
216     return linkMargin + aNoiseFloor;
217 }
218 
CostForLinkQuality(LinkQuality aLinkQuality)219 uint8_t CostForLinkQuality(LinkQuality aLinkQuality)
220 {
221     static const uint8_t kCostsForLinkQuality[] = {
222         kCostForLinkQuality0, // Link cost for `kLinkQuality0` (0).
223         kCostForLinkQuality1, // Link cost for `kLinkQuality1` (1).
224         kCostForLinkQuality2, // Link cost for `kLinkQuality2` (2).
225         kCostForLinkQuality3, // Link cost for `kLinkQuality3` (3).
226     };
227 
228     struct EnumCheck
229     {
230         InitEnumValidatorCounter();
231         ValidateNextEnum(kLinkQuality0);
232         ValidateNextEnum(kLinkQuality1);
233         ValidateNextEnum(kLinkQuality2);
234         ValidateNextEnum(kLinkQuality3);
235     };
236 
237     uint8_t cost = Mle::kMaxRouteCost;
238 
239     VerifyOrExit(aLinkQuality <= kLinkQuality3);
240     cost = kCostsForLinkQuality[aLinkQuality];
241 
242 exit:
243     return cost;
244 }
245 
CalculateLinkQuality(uint8_t aLinkMargin,uint8_t aLastLinkQuality)246 LinkQuality LinkQualityInfo::CalculateLinkQuality(uint8_t aLinkMargin, uint8_t aLastLinkQuality)
247 {
248     // Static private method to calculate the link quality from a given
249     // link margin while taking into account the last link quality
250     // value and adding the hysteresis value to the thresholds. If
251     // there is no previous value for link quality, the constant
252     // kNoLinkQuality should be passed as the second argument.
253 
254     uint8_t     threshold1, threshold2, threshold3;
255     LinkQuality linkQuality = kLinkQuality0;
256 
257     threshold1 = kThreshold1;
258     threshold2 = kThreshold2;
259     threshold3 = kThreshold3;
260 
261     // Apply the hysteresis threshold based on the last link quality value.
262 
263     switch (aLastLinkQuality)
264     {
265     case 0:
266         threshold1 += kHysteresisThreshold;
267 
268         OT_FALL_THROUGH;
269 
270     case 1:
271         threshold2 += kHysteresisThreshold;
272 
273         OT_FALL_THROUGH;
274 
275     case 2:
276         threshold3 += kHysteresisThreshold;
277 
278         OT_FALL_THROUGH;
279 
280     default:
281         break;
282     }
283 
284     if (aLinkMargin > threshold3)
285     {
286         linkQuality = kLinkQuality3;
287     }
288     else if (aLinkMargin > threshold2)
289     {
290         linkQuality = kLinkQuality2;
291     }
292     else if (aLinkMargin > threshold1)
293     {
294         linkQuality = kLinkQuality1;
295     }
296 
297     return linkQuality;
298 }
299 
300 } // namespace ot
301