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