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 #include "test_platform.h"
30 #include "test_util.h"
31
32 #include "common/array.hpp"
33 #include "common/code_utils.hpp"
34 #include "thread/link_metrics.hpp"
35 #include "thread/link_quality.hpp"
36
37 namespace ot {
38
39 static Instance *sInstance;
40
41 enum
42 {
43 kMaxRssValue = 0,
44 kMinRssValue = -128,
45
46 kStringBuffferSize = 80,
47
48 kRssAverageMaxDiff = 16,
49 kNumRssAdds = 300,
50
51 kRawAverageBitShift = 3,
52 kRawAverageMultiple = (1 << kRawAverageBitShift),
53 kRawAverageBitMask = (1 << kRawAverageBitShift) - 1,
54 };
55
56 #define MIN_RSS(_rss1, _rss2) (((_rss1) < (_rss2)) ? (_rss1) : (_rss2))
57 #define MAX_RSS(_rss1, _rss2) (((_rss1) < (_rss2)) ? (_rss2) : (_rss1))
58 #define ABS(value) (((value) >= 0) ? (value) : -(value))
59
60 // This struct contains RSS values and test data for checking link quality info class.
61 struct RssTestData
62 {
63 const int8_t *mRssList; // Array of RSS values.
64 size_t mRssListSize; // Size of RSS list.
65 uint8_t mExpectedLinkQuality; // Expected final link quality value.
66 };
67
68 int8_t sNoiseFloor = -100; // dBm
69
70 // Check and verify the raw average RSS value to match the value from GetAverage().
VerifyRawRssValue(int8_t aAverage,uint16_t aRawValue)71 void VerifyRawRssValue(int8_t aAverage, uint16_t aRawValue)
72 {
73 if (aAverage != Radio::kInvalidRssi)
74 {
75 VerifyOrQuit(aAverage == -static_cast<int16_t>((aRawValue + (kRawAverageMultiple / 2)) >> kRawAverageBitShift),
76 "Raw value does not match the average.");
77 }
78 else
79 {
80 VerifyOrQuit(aRawValue == 0, "Raw value does not match the average.");
81 }
82 }
83
84 // This function prints the values in the passed in link info instance. It is invoked as the final step in test-case.
PrintOutcome(LinkQualityInfo & aLinkInfo)85 void PrintOutcome(LinkQualityInfo &aLinkInfo) { printf("%s -> PASS \n", aLinkInfo.ToInfoString().AsCString()); }
86
TestLinkQualityData(RssTestData aRssData)87 void TestLinkQualityData(RssTestData aRssData)
88 {
89 LinkQualityInfo linkInfo;
90 int8_t rss, ave, min, max;
91 size_t i;
92
93 sInstance = testInitInstance();
94 VerifyOrQuit(sInstance != nullptr);
95 linkInfo.Init(*sInstance);
96
97 printf("- - - - - - - - - - - - - - - - - -\n");
98 linkInfo.Clear();
99 min = kMinRssValue;
100 max = kMaxRssValue;
101
102 for (i = 0; i < aRssData.mRssListSize; i++)
103 {
104 rss = aRssData.mRssList[i];
105 min = MIN_RSS(rss, min);
106 max = MAX_RSS(rss, max);
107 linkInfo.AddRss(rss);
108 VerifyOrQuit(linkInfo.GetLastRss() == rss);
109 ave = linkInfo.GetAverageRss();
110 VerifyOrQuit(ave >= min, "GetAverageRss() is smaller than min value.");
111 VerifyOrQuit(ave <= max, "GetAverageRss() is larger than min value");
112 VerifyRawRssValue(linkInfo.GetAverageRss(), linkInfo.GetAverageRssRaw());
113 printf("%02u) AddRss(%4d): ", static_cast<unsigned int>(i), rss);
114 PrintOutcome(linkInfo);
115 }
116
117 VerifyOrQuit(linkInfo.GetLinkQuality() == aRssData.mExpectedLinkQuality);
118 }
119
120 // Check and verify the raw average RSS value to match the value from GetAverage().
VerifyRawRssValue(RssAverager & aRssAverager)121 void VerifyRawRssValue(RssAverager &aRssAverager)
122 {
123 int8_t average = aRssAverager.GetAverage();
124 uint16_t rawValue = aRssAverager.GetRaw();
125
126 if (average != Radio::kInvalidRssi)
127 {
128 VerifyOrQuit(average == -static_cast<int16_t>((rawValue + (kRawAverageMultiple / 2)) >> kRawAverageBitShift),
129 "Raw value does not match the average.");
130 }
131 else
132 {
133 VerifyOrQuit(rawValue == 0, "Raw value does not match the average.");
134 }
135 }
136
137 // This function prints the values in the passed link info instance. It is invoked as the final step in test-case.
PrintOutcome(RssAverager & aRssAverager)138 void PrintOutcome(RssAverager &aRssAverager) { printf("%s -> PASS\n", aRssAverager.ToString().AsCString()); }
139
GetRandomRss(void)140 int8_t GetRandomRss(void)
141 {
142 uint32_t value;
143
144 value = rand() % 128;
145 return -static_cast<int8_t>(value);
146 }
147
TestRssAveraging(void)148 void TestRssAveraging(void)
149 {
150 RssAverager rssAverager;
151 int8_t rss, rss2, ave;
152 int16_t diff;
153 size_t i, j, k;
154 const int8_t rssValues[] = {kMinRssValue, -70, -40, -41, -10, kMaxRssValue};
155 int16_t sum;
156
157 //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
158 // Values after initialization/reset.
159
160 rssAverager.Clear();
161
162 printf("\nAfter Clear: ");
163 VerifyOrQuit(rssAverager.GetAverage() == Radio::kInvalidRssi, "Initial value from GetAverage() is incorrect.");
164 VerifyRawRssValue(rssAverager);
165 PrintOutcome(rssAverager);
166
167 //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
168 // Adding a single value
169 rss = -70;
170 printf("AddRss(%d): ", rss);
171 IgnoreError(rssAverager.Add(rss));
172 VerifyOrQuit(rssAverager.GetAverage() == rss, "GetAverage() failed after a single AddRss().");
173 VerifyRawRssValue(rssAverager);
174 PrintOutcome(rssAverager);
175
176 //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
177 // Clear
178
179 printf("Clear(): ");
180 rssAverager.Clear();
181 VerifyOrQuit(rssAverager.GetAverage() == Radio::kInvalidRssi, "GetAverage() after Clear() is incorrect.");
182 VerifyRawRssValue(rssAverager);
183 PrintOutcome(rssAverager);
184
185 //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
186 // Adding the same value many times.
187
188 printf("- - - - - - - - - - - - - - - - - -\n");
189
190 for (j = 0; j < sizeof(rssValues); j++)
191 {
192 rssAverager.Clear();
193 rss = rssValues[j];
194 printf("AddRss(%4d) %d times: ", rss, kNumRssAdds);
195
196 for (i = 0; i < kNumRssAdds; i++)
197 {
198 IgnoreError(rssAverager.Add(rss));
199 VerifyOrQuit(rssAverager.GetAverage() == rss, "GetAverage() returned incorrect value.");
200 VerifyRawRssValue(rssAverager);
201 }
202
203 PrintOutcome(rssAverager);
204 }
205
206 //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
207 // Adding two RSS values:
208
209 printf("- - - - - - - - - - - - - - - - - -\n");
210
211 for (j = 0; j < sizeof(rssValues); j++)
212 {
213 rss = rssValues[j];
214
215 for (k = 0; k < sizeof(rssValues); k++)
216 {
217 if (k == j)
218 {
219 continue;
220 }
221
222 rss2 = rssValues[k];
223 rssAverager.Clear();
224 IgnoreError(rssAverager.Add(rss));
225 IgnoreError(rssAverager.Add(rss2));
226 printf("AddRss(%4d), AddRss(%4d): ", rss, rss2);
227 VerifyOrQuit(rssAverager.GetAverage() == ((rss + rss2) >> 1), "GetAverage() returned incorrect value.");
228 VerifyRawRssValue(rssAverager);
229 PrintOutcome(rssAverager);
230 }
231 }
232
233 //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
234 // Adding one value many times and a different value once:
235
236 printf("- - - - - - - - - - - - - - - - - -\n");
237
238 for (j = 0; j < sizeof(rssValues); j++)
239 {
240 rss = rssValues[j];
241
242 for (k = 0; k < sizeof(rssValues); k++)
243 {
244 if (k == j)
245 {
246 continue;
247 }
248
249 rss2 = rssValues[k];
250 rssAverager.Clear();
251
252 for (i = 0; i < kNumRssAdds; i++)
253 {
254 IgnoreError(rssAverager.Add(rss));
255 }
256
257 IgnoreError(rssAverager.Add(rss2));
258 printf("AddRss(%4d) %d times, AddRss(%4d): ", rss, kNumRssAdds, rss2);
259 ave = rssAverager.GetAverage();
260 VerifyOrQuit(ave >= MIN_RSS(rss, rss2), "GetAverage() returned incorrect value.");
261 VerifyOrQuit(ave <= MAX_RSS(rss, rss2), "GetAverage() returned incorrect value.");
262 VerifyRawRssValue(rssAverager);
263 PrintOutcome(rssAverager);
264 }
265 }
266
267 //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
268 // Adding two alternating values many times:
269
270 printf("- - - - - - - - - - - - - - - - - -\n");
271
272 for (j = 0; j < sizeof(rssValues); j++)
273 {
274 rss = rssValues[j];
275
276 for (k = 0; k < sizeof(rssValues); k++)
277 {
278 if (k == j)
279 {
280 continue;
281 }
282
283 rss2 = rssValues[k];
284 rssAverager.Clear();
285
286 for (i = 0; i < kNumRssAdds; i++)
287 {
288 IgnoreError(rssAverager.Add(rss));
289 IgnoreError(rssAverager.Add(rss2));
290 ave = rssAverager.GetAverage();
291 VerifyOrQuit(ave >= MIN_RSS(rss, rss2), "GetAverage() is smaller than min value.");
292 VerifyOrQuit(ave <= MAX_RSS(rss, rss2), "GetAverage() is larger than min value.");
293 diff = ave;
294 diff -= (rss + rss2) >> 1;
295 VerifyOrQuit(ABS(diff) <= kRssAverageMaxDiff, "GetAverage() is incorrect");
296 VerifyRawRssValue(rssAverager);
297 }
298
299 printf("[AddRss(%4d), AddRss(%4d)] %d times: ", rss, rss2, kNumRssAdds);
300 PrintOutcome(rssAverager);
301 }
302 }
303
304 //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
305 // For the first 8 values the average should be the arithmetic mean.
306
307 printf("- - - - - - - - - - - - - - - - - -\n");
308
309 for (i = 0; i < 1000; i++)
310 {
311 double mean;
312
313 rssAverager.Clear();
314 sum = 0;
315
316 printf("\n");
317
318 for (j = 1; j <= 8; j++)
319 {
320 rss = GetRandomRss();
321 IgnoreError(rssAverager.Add(rss));
322 sum += rss;
323 mean = static_cast<double>(sum) / static_cast<double>(j);
324 VerifyOrQuit(ABS(rssAverager.GetAverage() - mean) < 1, "Average does not match the arithmetic mean!");
325 VerifyRawRssValue(rssAverager);
326 printf("AddRss(%4d) sum=%-5d, mean=%-8.2f RssAverager=", rss, sum, mean);
327 PrintOutcome(rssAverager);
328 }
329 }
330 }
331
TestLinkQualityCalculations(void)332 void TestLinkQualityCalculations(void)
333 {
334 const int8_t rssList1[] = {-81, -80, -79, -78, -76, -80, -77, -75, -77, -76, -77, -74};
335 const RssTestData rssData1 = {
336 rssList1, // mRssList
337 sizeof(rssList1), // mRssListSize
338 3 // mExpectedLinkQuality
339 };
340
341 const int8_t rssList2[] = {-90, -80, -85};
342 const RssTestData rssData2 = {
343 rssList2, // mRssList
344 sizeof(rssList2), // mRssListSize
345 2 // mExpectedLinkQuality
346 };
347
348 const int8_t rssList3[] = {-95, -96, -98, -99, -100, -100, -98, -99, -100, -100, -100, -100, -100};
349 const RssTestData rssData3 = {
350 rssList3, // mRssList
351 sizeof(rssList3), // mRssListSize
352 0 // mExpectedLinkQuality
353 };
354
355 const int8_t rssList4[] = {-75, -100, -100, -100, -100, -100, -95, -92, -93, -94, -93, -93};
356 const RssTestData rssData4 = {
357 rssList4, // mRssList
358 sizeof(rssList4), // mRssListSize
359 1 // mExpectedLinkQuality
360 };
361
362 TestLinkQualityData(rssData1);
363 TestLinkQualityData(rssData2);
364 TestLinkQualityData(rssData3);
365 TestLinkQualityData(rssData4);
366 }
367
TestSuccessRateTracker(void)368 void TestSuccessRateTracker(void)
369 {
370 SuccessRateTracker rateTracker;
371 uint16_t sampleCount;
372
373 const uint16_t kMaxSamples = 5000;
374
375 const uint16_t kMaxRate = SuccessRateTracker::kMaxRateValue;
376 const double kMaxError = 1.0; // Max permitted error in percentage
377 const uint16_t kWeightLimit[] = {64, 128, 256, 300, 512, 810, 900};
378
379 printf("\nTesting SuccessRateTracker\n");
380
381 rateTracker.Clear();
382
383 VerifyOrQuit(rateTracker.GetSuccessRate() == kMaxRate, "SuccessRateTracker: Initial value incorrect");
384 VerifyOrQuit(rateTracker.GetFailureRate() == 0, "SuccessRateTracker: Initial value incorrect");
385
386 // Adding all success
387 for (sampleCount = 1; sampleCount < kMaxSamples; sampleCount++)
388 {
389 rateTracker.AddSample(true, sampleCount);
390
391 VerifyOrQuit(rateTracker.GetSuccessRate() == kMaxRate, "SuccessRateTracker: incorrect rate all success case");
392 VerifyOrQuit(rateTracker.GetFailureRate() == 0, "SuccessRateTracker: incorrect rate in all success case");
393 }
394
395 rateTracker.Clear();
396 VerifyOrQuit(rateTracker.GetSuccessRate() == kMaxRate, "SuccessRateTracker: Rate incorrect after reset");
397 VerifyOrQuit(rateTracker.GetFailureRate() == 0, "SuccessRateTracker: Rate incorrect after reset");
398
399 // Adding all failures
400 for (sampleCount = 1; sampleCount < kMaxRate; sampleCount++)
401 {
402 rateTracker.AddSample(false, sampleCount);
403
404 VerifyOrQuit(rateTracker.GetSuccessRate() == 0, "SuccessRateTracker: rate incorrect all failure case");
405 VerifyOrQuit(rateTracker.GetFailureRate() == kMaxRate,
406 "SuccessRateTracker: rate incorrect in all failure case");
407 }
408
409 // Adding success/failure at different rates and checking the RateTracker rate for every sample
410
411 for (uint16_t testRound = 0; testRound < GetArrayLength(kWeightLimit) * 2; testRound++)
412 {
413 uint16_t weightLimit;
414 bool reverseLogic;
415 double maxDiff = 0;
416
417 // Reverse the logic (add success instead of failure) on even test rounds
418 reverseLogic = ((testRound % 2) == 0);
419
420 // Select a different weight limit based on the current test round
421 weightLimit = kWeightLimit[testRound / 2];
422
423 printf("TestRound %02d, weightLimit %3d, reverseLogic %d ", testRound, weightLimit, reverseLogic);
424
425 for (uint16_t period = 1; period < 101; period++)
426 {
427 uint16_t failureCount = 0;
428
429 rateTracker.Clear();
430
431 for (sampleCount = 1; sampleCount < kMaxSamples; sampleCount++)
432 {
433 double expectedRate;
434 double failureRate;
435 double diff;
436 bool isSuccess = ((sampleCount % period) == 0);
437 uint16_t weight;
438
439 if (reverseLogic)
440 {
441 isSuccess = !isSuccess;
442 }
443
444 weight = sampleCount;
445
446 if (weight > weightLimit)
447 {
448 weight = weightLimit;
449 }
450
451 rateTracker.AddSample(isSuccess, weight);
452
453 if (!isSuccess)
454 {
455 failureCount++;
456 }
457
458 // Calculate the failure rate from rateTracker and expected rate.
459
460 failureRate = static_cast<double>(rateTracker.GetFailureRate()) * 100.0 / kMaxRate; // in percent
461 expectedRate = static_cast<double>(failureCount) * 100.0 / sampleCount; // in percent
462
463 diff = failureRate - expectedRate;
464 diff = ABS(diff);
465
466 VerifyOrQuit(diff <= kMaxError, "SuccessRateTracker: rate does not match expected value");
467
468 if (diff > maxDiff)
469 {
470 maxDiff = diff;
471 }
472 }
473 }
474
475 printf(" MaxDiff = %.3f%%-> PASS\n", maxDiff);
476 }
477 }
478
479 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
480
481 class UnitTester
482 {
483 public:
TestLinkMetricsScaling(void)484 static void TestLinkMetricsScaling(void)
485 {
486 printf("\nTestLinkMetricsScaling\n");
487
488 // Test Link Margin scaling from [0,130] -> [0, 255]
489
490 for (uint8_t linkMargin = 0; linkMargin <= 130; linkMargin++)
491 {
492 double scaled = 255.0 / 130.0 * linkMargin;
493 uint8_t scaledAsU8 = static_cast<uint8_t>(scaled + 0.5);
494
495 printf("\nLinkMargin : %-3u -> Scaled : %.1f (rounded:%u)", linkMargin, scaled, scaledAsU8);
496
497 VerifyOrQuit(LinkMetrics::ScaleLinkMarginToRawValue(linkMargin) == scaledAsU8);
498 VerifyOrQuit(LinkMetrics::ScaleRawValueToLinkMargin(scaledAsU8) == linkMargin);
499 }
500
501 VerifyOrQuit(LinkMetrics::ScaleLinkMarginToRawValue(131) == 255);
502 VerifyOrQuit(LinkMetrics::ScaleLinkMarginToRawValue(150) == 255);
503 VerifyOrQuit(LinkMetrics::ScaleLinkMarginToRawValue(255) == 255);
504
505 // Test RSSI scaling from [-130, 0] -> [0, 255]
506
507 for (int8_t rssi = -128; rssi <= 0; rssi++)
508 {
509 double scaled = 255.0 / 130.0 * (rssi + 130.0);
510 uint8_t scaledAsU8 = static_cast<uint8_t>(scaled + 0.5);
511
512 printf("\nRSSI : %-3d -> Scaled :%.1f (rounded:%u)", rssi, scaled, scaledAsU8);
513
514 VerifyOrQuit(LinkMetrics::ScaleRssiToRawValue(rssi) == scaledAsU8);
515 VerifyOrQuit(LinkMetrics::ScaleRawValueToRssi(scaledAsU8) == rssi);
516 }
517
518 VerifyOrQuit(LinkMetrics::ScaleRssiToRawValue(1) == 255);
519 VerifyOrQuit(LinkMetrics::ScaleRssiToRawValue(10) == 255);
520 VerifyOrQuit(LinkMetrics::ScaleRssiToRawValue(127) == 255);
521
522 // Test corner case of ScaleRawValueToRssi
523 for (uint8_t rawValue = 0; rawValue < 2; rawValue++)
524 {
525 int8_t rssi = LinkMetrics::ScaleRawValueToRssi(rawValue);
526 printf("\nRaw Value: %u -> RSSI : %-3d", rawValue, rssi);
527 }
528 }
529 };
530
531 #endif
532
533 } // namespace ot
534
main(void)535 int main(void)
536 {
537 ot::TestRssAveraging();
538 ot::TestLinkQualityCalculations();
539 ot::TestSuccessRateTracker();
540 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
541 ot::UnitTester::TestLinkMetricsScaling();
542 #endif
543
544 printf("\nAll tests passed\n");
545 return 0;
546 }
547