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