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