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