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 <stdio.h>
37 
38 #include "common/code_utils.hpp"
39 #include "common/locator_getters.hpp"
40 #include "common/num_utils.hpp"
41 #include "common/numeric_limits.hpp"
42 #include "instance/instance.hpp"
43 
44 namespace ot {
45 
46 // This array gives the decimal point digits representing 0/8, 1/8, ..., 7/8 (does not include the '.').
47 static const char *const kDigitsString[8] = {
48     // 0/8,  1/8,   2/8,   3/8,   4/8,   5/8,   6/8,   7/8
49     "0", "125", "25", "375", "5", "625", "75", "875"};
50 
AddSample(bool aSuccess,uint16_t aWeight)51 void SuccessRateTracker::AddSample(bool aSuccess, uint16_t aWeight)
52 {
53     uint32_t oldAverage = mFailureRate;
54     uint32_t newValue   = (aSuccess) ? 0 : kMaxRateValue;
55     uint32_t n          = aWeight;
56 
57     // `n/2` is added to the sum to ensure rounding the value to the nearest integer when dividing by `n`
58     // (e.g., 1.2 -> 1, 3.5 -> 4).
59 
60     mFailureRate = static_cast<uint16_t>(((oldAverage * (n - 1)) + newValue + (n / 2)) / n);
61 }
62 
Add(int8_t aRss)63 Error RssAverager::Add(int8_t aRss)
64 {
65     Error    error = kErrorNone;
66     uint16_t newValue;
67 
68     VerifyOrExit(aRss != Radio::kInvalidRssi, error = kErrorInvalidArgs);
69 
70     // Restrict the RSS value to the closed range [-128, 0]
71     // so the RSS times precision multiple can fit in 11 bits.
72     aRss = Min<int8_t>(aRss, 0);
73 
74     // Multiply the RSS value by a precision multiple (currently -8).
75 
76     newValue = static_cast<uint16_t>(-aRss);
77     newValue <<= kPrecisionBitShift;
78 
79     mCount += (mCount < (1 << kCoeffBitShift));
80     // Maintain arithmetic mean.
81     // newAverage = newValue * (1/mCount) + oldAverage * ((mCount -1)/mCount)
82     mAverage = static_cast<uint16_t>(((mAverage * (mCount - 1)) + newValue) / mCount);
83 
84 exit:
85     return error;
86 }
87 
GetAverage(void) const88 int8_t RssAverager::GetAverage(void) const
89 {
90     int8_t average;
91 
92     VerifyOrExit(mCount != 0, average = Radio::kInvalidRssi);
93 
94     average = -static_cast<int8_t>(mAverage >> kPrecisionBitShift);
95 
96     // Check for possible round up (e.g., average of -71.5 --> -72)
97 
98     if ((mAverage & kPrecisionBitMask) >= (kPrecision >> 1))
99     {
100         average--;
101     }
102 
103 exit:
104     return average;
105 }
106 
ToString(void) const107 RssAverager::InfoString RssAverager::ToString(void) const
108 {
109     InfoString string;
110 
111     VerifyOrExit(mCount != 0);
112     string.Append("%d.%s", -(mAverage >> kPrecisionBitShift), kDigitsString[mAverage & kPrecisionBitMask]);
113 
114 exit:
115     return string;
116 }
117 
Add(uint8_t aLqi)118 void LqiAverager::Add(uint8_t aLqi)
119 {
120     uint8_t  count;
121     uint16_t newAverage;
122 
123     if (mCount < NumericLimits<uint8_t>::kMax)
124     {
125         mCount++;
126     }
127 
128     count = Min(static_cast<uint8_t>(1 << kCoeffBitShift), mCount);
129 
130     newAverage = mAverage;
131     newAverage = (newAverage * (count - 1) + aLqi) / count;
132 
133     mAverage = static_cast<uint8_t>(newAverage);
134 }
135 
Clear(void)136 void LinkQualityInfo::Clear(void)
137 {
138     mRssAverager.Clear();
139     SetLinkQuality(kLinkQuality0);
140     mLastRss = Radio::kInvalidRssi;
141 
142     mFrameErrorRate.Clear();
143     mMessageErrorRate.Clear();
144 }
145 
AddRss(int8_t aRss)146 void LinkQualityInfo::AddRss(int8_t aRss)
147 {
148     uint8_t oldLinkQuality = kNoLinkQuality;
149 
150     VerifyOrExit(aRss != Radio::kInvalidRssi);
151 
152     mLastRss = aRss;
153 
154     if (mRssAverager.HasAverage())
155     {
156         oldLinkQuality = GetLinkQuality();
157     }
158 
159     SuccessOrExit(mRssAverager.Add(aRss));
160 
161     SetLinkQuality(CalculateLinkQuality(GetLinkMargin(), oldLinkQuality));
162 
163 exit:
164     return;
165 }
166 
GetLinkMargin(void) const167 uint8_t LinkQualityInfo::GetLinkMargin(void) const
168 {
169     return ComputeLinkMargin(Get<Mac::SubMac>().GetNoiseFloor(), GetAverageRss());
170 }
171 
ToInfoString(void) const172 LinkQualityInfo::InfoString LinkQualityInfo::ToInfoString(void) const
173 {
174     InfoString string;
175 
176     string.Append("aveRss:%s, lastRss:%d, linkQuality:%d", mRssAverager.ToString().AsCString(), GetLastRss(),
177                   GetLinkQuality());
178 
179     return string;
180 }
181 
ComputeLinkMargin(int8_t aNoiseFloor,int8_t aRss)182 uint8_t ComputeLinkMargin(int8_t aNoiseFloor, int8_t aRss)
183 {
184     int8_t linkMargin = aRss - aNoiseFloor;
185 
186     if (linkMargin < 0 || aRss == Radio::kInvalidRssi)
187     {
188         linkMargin = 0;
189     }
190 
191     return static_cast<uint8_t>(linkMargin);
192 }
193 
LinkQualityForLinkMargin(uint8_t aLinkMargin)194 LinkQuality LinkQualityForLinkMargin(uint8_t aLinkMargin)
195 {
196     return LinkQualityInfo::CalculateLinkQuality(aLinkMargin, LinkQualityInfo::kNoLinkQuality);
197 }
198 
GetTypicalRssForLinkQuality(int8_t aNoiseFloor,LinkQuality aLinkQuality)199 int8_t GetTypicalRssForLinkQuality(int8_t aNoiseFloor, LinkQuality aLinkQuality)
200 {
201     int8_t linkMargin = 0;
202 
203     switch (aLinkQuality)
204     {
205     case kLinkQuality3:
206         linkMargin = LinkQualityInfo::kLinkQuality3LinkMargin;
207         break;
208 
209     case kLinkQuality2:
210         linkMargin = LinkQualityInfo::kLinkQuality2LinkMargin;
211         break;
212 
213     case kLinkQuality1:
214         linkMargin = LinkQualityInfo::kLinkQuality1LinkMargin;
215         break;
216 
217     default:
218         linkMargin = LinkQualityInfo::kLinkQuality0LinkMargin;
219         break;
220     }
221 
222     return linkMargin + aNoiseFloor;
223 }
224 
CostForLinkQuality(LinkQuality aLinkQuality)225 uint8_t CostForLinkQuality(LinkQuality aLinkQuality)
226 {
227     static const uint8_t kCostsForLinkQuality[] = {
228         kCostForLinkQuality0, // Link cost for `kLinkQuality0` (0).
229         kCostForLinkQuality1, // Link cost for `kLinkQuality1` (1).
230         kCostForLinkQuality2, // Link cost for `kLinkQuality2` (2).
231         kCostForLinkQuality3, // Link cost for `kLinkQuality3` (3).
232     };
233 
234     static_assert(kLinkQuality0 == 0, "kLinkQuality0 is invalid");
235     static_assert(kLinkQuality1 == 1, "kLinkQuality1 is invalid");
236     static_assert(kLinkQuality2 == 2, "kLinkQuality2 is invalid");
237     static_assert(kLinkQuality3 == 3, "kLinkQuality3 is invalid");
238 
239     uint8_t cost = Mle::kMaxRouteCost;
240 
241     VerifyOrExit(aLinkQuality <= kLinkQuality3);
242     cost = kCostsForLinkQuality[aLinkQuality];
243 
244 exit:
245     return cost;
246 }
247 
CalculateLinkQuality(uint8_t aLinkMargin,uint8_t aLastLinkQuality)248 LinkQuality LinkQualityInfo::CalculateLinkQuality(uint8_t aLinkMargin, uint8_t aLastLinkQuality)
249 {
250     // Static private method to calculate the link quality from a given
251     // link margin while taking into account the last link quality
252     // value and adding the hysteresis value to the thresholds. If
253     // there is no previous value for link quality, the constant
254     // kNoLinkQuality should be passed as the second argument.
255 
256     uint8_t     threshold1, threshold2, threshold3;
257     LinkQuality linkQuality = kLinkQuality0;
258 
259     threshold1 = kThreshold1;
260     threshold2 = kThreshold2;
261     threshold3 = kThreshold3;
262 
263     // Apply the hysteresis threshold based on the last link quality value.
264 
265     switch (aLastLinkQuality)
266     {
267     case 0:
268         threshold1 += kHysteresisThreshold;
269 
270         OT_FALL_THROUGH;
271 
272     case 1:
273         threshold2 += kHysteresisThreshold;
274 
275         OT_FALL_THROUGH;
276 
277     case 2:
278         threshold3 += kHysteresisThreshold;
279 
280         OT_FALL_THROUGH;
281 
282     default:
283         break;
284     }
285 
286     if (aLinkMargin > threshold3)
287     {
288         linkQuality = kLinkQuality3;
289     }
290     else if (aLinkMargin > threshold2)
291     {
292         linkQuality = kLinkQuality2;
293     }
294     else if (aLinkMargin > threshold1)
295     {
296         linkQuality = kLinkQuality1;
297     }
298 
299     return linkQuality;
300 }
301 
302 } // namespace ot
303