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 includes definitions for storing and processing link quality information.
32  */
33 
34 #ifndef LINK_QUALITY_HPP_
35 #define LINK_QUALITY_HPP_
36 
37 #include "openthread-core-config.h"
38 
39 #include <openthread/platform/radio.h>
40 
41 #include "common/clearable.hpp"
42 #include "common/locator.hpp"
43 #include "common/string.hpp"
44 #include "thread/mle_types.hpp"
45 
46 namespace ot {
47 
48 /**
49  * @addtogroup core-link-quality
50  *
51  * @brief
52  *   This module includes definitions for Thread link quality metrics.
53  *
54  * @{
55  */
56 
57 /**
58  * Implements an operation Success Rate Tracker.
59  *
60  * This can be used to track different link quality related metrics, e.g., CCA failure rate, frame tx success rate).
61  * The success rate is maintained using an exponential moving IIR averaging filter with a `uint16_t` as the storage.
62  *
63  */
64 class SuccessRateTracker : public Clearable<SuccessRateTracker>
65 {
66 public:
67     static constexpr uint16_t kMaxRateValue = 0xffff; ///< Value corresponding to max (failure/success) rate of 100%.
68 
69     /**
70      * Adds a sample (success or failure) to `SuccessRateTracker`.
71      *
72      * @param[in] aSuccess   The sample status be added, `true` for success, `false` for failure.
73      * @param[in] aWeight    The weight coefficient used for adding the new sample into average.
74      *
75      */
76     void AddSample(bool aSuccess, uint16_t aWeight = kDefaultWeight);
77 
78     /**
79      * Returns the average failure rate.
80      *
81      * @retval the average failure rate `[0-kMaxRateValue]` with `kMaxRateValue` corresponding to 100%.
82      *
83      */
GetFailureRate(void) const84     uint16_t GetFailureRate(void) const { return mFailureRate; }
85 
86     /**
87      * Returns the average success rate.
88      *
89      * @retval the average success rate as [0-kMaxRateValue] with `kMaxRateValue` corresponding to 100%.
90      *
91      */
GetSuccessRate(void) const92     uint16_t GetSuccessRate(void) const { return kMaxRateValue - mFailureRate; }
93 
94 private:
95     static constexpr uint16_t kDefaultWeight = 64;
96 
97     uint16_t mFailureRate;
98 };
99 
100 /**
101  * Implements a Received Signal Strength (RSS) averager.
102  *
103  * The average is maintained using an adaptive exponentially weighted moving filter.
104  *
105  */
106 class RssAverager : public Clearable<RssAverager>
107 {
108 public:
109     static constexpr uint16_t kStringSize = 10; ///< Max string size for average (@sa ToString()).
110 
111     /**
112      * Defines the fixed-length `String` object returned from `ToString()`.
113      *
114      */
115     typedef String<kStringSize> InfoString;
116 
117     /**
118      * Indicates whether the averager contains an average (i.e., at least one RSS value has been added).
119      *
120      * @retval true   If the average value is available (at least one RSS value has been added).
121      * @retval false  Averager is empty (no RSS value added yet).
122      *
123      */
HasAverage(void) const124     bool HasAverage(void) const { return (mCount != 0); }
125 
126     /**
127      * Adds a received signal strength (RSS) value to the average.
128      *
129      * If @p aRss is `Radio::kInvalidRssi`, it is ignored and error status kErrorInvalidArgs is returned.
130      * The value of RSS is capped at 0dBm (i.e., for any given RSS value higher than 0dBm, 0dBm is used instead).
131      *
132      * @param[in] aRss                Received signal strength value (in dBm) to be added to the average.
133      *
134      * @retval kErrorNone         New RSS value added to average successfully.
135      * @retval kErrorInvalidArgs  Value of @p aRss is `Radio::kInvalidRssi`.
136      *
137      */
138     Error Add(int8_t aRss);
139 
140     /**
141      * Returns the current average signal strength value maintained by the averager.
142      *
143      * @returns The current average value (in dBm) or `Radio::kInvalidRssi` if no average is available.
144      *
145      */
146     int8_t GetAverage(void) const;
147 
148     /**
149      * Returns an raw/encoded version of current average signal strength value. The raw value is the
150      * average multiplied by a precision factor (currently set as -8).
151      *
152      * @returns The current average multiplied by precision factor or zero if no average is available.
153      *
154      */
GetRaw(void) const155     uint16_t GetRaw(void) const { return mAverage; }
156 
157     /**
158      * Converts the current average RSS value to a human-readable string (e.g., "-80.375"). If the
159      * average is unknown, empty string is returned.
160      *
161      * @returns An `InfoString` object containing the string representation of average RSS.
162      *
163      */
164     InfoString ToString(void) const;
165 
166 private:
167     /*
168      * The RssAverager uses an adaptive exponentially weighted filter to maintain the average. It keeps track of
169      * current average and the number of added RSS values (up to a 8).
170      *
171      * For the first 8 added RSS values, the average is the arithmetic mean of the added values (e.g., if 5 values are
172      * added, the average is sum of the 5 added RSS values divided by 5. After the 8th RSS value, a weighted filter is
173      * used with coefficients (1/8, 7/8), i.e., newAverage = 1/8 * newRss + 7/8 * oldAverage.
174      *
175      * To add to accuracy of the averaging process, the RSS values and the maintained average are multiplied by a
176      * precision factor of -8.
177      *
178      */
179     static constexpr uint8_t kPrecisionBitShift = 3; // Precision multiple for RSS average (1 << PrecisionBitShift).
180     static constexpr uint8_t kPrecision         = (1 << kPrecisionBitShift);
181     static constexpr uint8_t kPrecisionBitMask  = (kPrecision - 1);
182     static constexpr uint8_t kCoeffBitShift     = 3; // Coeff for exp weighted filter (1 << kCoeffBitShift).
183 
184     // Member variables fit into two bytes.
185 
186     uint16_t mAverage : 11; // The raw average signal strength value (stored as RSS times precision multiple).
187     uint16_t mCount : 5;    // Number of RSS values added to averager so far (limited to 2^kCoeffBitShift-1).
188 };
189 
190 /**
191  * Implements a Link Quality Indicator (LQI) averager.
192  *
193  * It maintains the exponential moving average value of LQI.
194  *
195  */
196 class LqiAverager : public Clearable<LqiAverager>
197 {
198 public:
199     /**
200      * Adds a link quality indicator (LQI) value to the average.
201      *
202      * @param[in] aLqi  Link Quality Indicator value to be added to the average.
203      *
204      */
205     void Add(uint8_t aLqi);
206 
207     /**
208      * Returns the current average link quality value maintained by the averager.
209      *
210      * @returns The current average value.
211      *
212      */
GetAverage(void) const213     uint8_t GetAverage(void) const { return mAverage; }
214 
215     /**
216      * Returns the count of frames calculated so far.
217      *
218      * @returns The count of frames calculated.
219      *
220      */
GetCount(void) const221     uint8_t GetCount(void) const { return mCount; }
222 
223 private:
224     static constexpr uint8_t kCoeffBitShift = 3; // Coeff used for exp weighted filter (1 << kCoeffBitShift).
225 
226     uint8_t mAverage; // The average link quality indicator value.
227     uint8_t mCount;   // Number of LQI values added to averager so far.
228 };
229 
230 /**
231  * Represents the link quality constants.
232  *
233  * Link Quality is an integer in [0, 3]. A higher link quality indicates a more usable link, with 0 indicating that the
234  * link is non-existent or unusable.
235  *
236  */
237 enum LinkQuality : uint8_t
238 {
239     kLinkQuality0 = 0, ///< Link quality 0 (non-existent link)
240     kLinkQuality1 = 1, ///< Link quality 1
241     kLinkQuality2 = 2, ///< Link quality 2
242     kLinkQuality3 = 3, ///< Link quality 3
243 };
244 
245 constexpr uint8_t kCostForLinkQuality0 = Mle::kMaxRouteCost; ///< Link Cost for Link Quality 0.
246 constexpr uint8_t kCostForLinkQuality1 = 4;                  ///< Link Cost for Link Quality 1.
247 constexpr uint8_t kCostForLinkQuality2 = 2;                  ///< Link Cost for Link Quality 2.
248 constexpr uint8_t kCostForLinkQuality3 = 1;                  ///< Link Cost for Link Quality 3.
249 
250 /**
251  * Converts link quality to route cost.
252  *
253  * @param[in]  aLinkQuality  The link quality to convert.
254  *
255  * @returns The route cost corresponding to @p aLinkQuality.
256  *
257  */
258 uint8_t CostForLinkQuality(LinkQuality aLinkQuality);
259 
260 /**
261  * Computes the link margin from a given noise floor and received signal strength.
262  *
263  * @param[in]  aNoiseFloor  The noise floor value (in dBm).
264  * @param[in]  aRss         The received signal strength value (in dBm).
265  *
266  * @returns The link margin value in dB.
267  *
268  */
269 uint8_t ComputeLinkMargin(int8_t aNoiseFloor, int8_t aRss);
270 
271 /**
272  * Converts a link margin value to a link quality value.
273  *
274  * @param[in]  aLinkMargin  The Link Margin in dB.
275  *
276  * @returns The link quality value (0-3).
277  *
278  */
279 LinkQuality LinkQualityForLinkMargin(uint8_t aLinkMargin);
280 
281 /**
282  * Gets the typical received signal strength value for a given link quality.
283  *
284  * @param[in]  aNoiseFloor   The noise floor value (in dBm).
285  * @param[in]  aLinkQuality  The link quality value in [0, 3].
286  *
287  * @returns The typical platform RSSI in dBm.
288  *
289  */
290 int8_t GetTypicalRssForLinkQuality(int8_t aNoiseFloor, LinkQuality aLinkQuality);
291 
292 /**
293  * Encapsulates/stores all relevant information about quality of a link, including average received signal
294  * strength (RSS), last RSS, link margin, and link quality.
295  *
296  */
297 class LinkQualityInfo : public InstanceLocatorInit
298 {
299     friend LinkQuality LinkQualityForLinkMargin(uint8_t aLinkMargin);
300     friend int8_t      GetTypicalRssForLinkQuality(int8_t aNoiseFloor, LinkQuality aLinkQuality);
301 
302 public:
303     static constexpr uint16_t kInfoStringSize = 50; ///< `InfoString` size (@sa ToInfoString()).
304 
305     /**
306      * Defines the fixed-length `String` object returned from `ToInfoString()`.
307      *
308      */
309     typedef String<kInfoStringSize> InfoString;
310 
311     /**
312      * Initializes the `LinkQualityInfo` object.
313      *
314      * @param[in] aInstance  A reference to the OpenThread instance.
315      *
316      */
Init(Instance & aInstance)317     void Init(Instance &aInstance) { InstanceLocatorInit::Init(aInstance); }
318 
319     /**
320      * Clears the all the data in the object.
321      *
322      */
323     void Clear(void);
324 
325     /**
326      * Clears the average RSS value.
327      *
328      */
ClearAverageRss(void)329     void ClearAverageRss(void) { mRssAverager.Clear(); }
330 
331     /**
332      * Adds a new received signal strength (RSS) value to the average.
333      *
334      * @param[in] aRss         A new received signal strength value (in dBm) to be added to the average.
335      *
336      */
337     void AddRss(int8_t aRss);
338 
339     /**
340      * Returns the current average received signal strength value.
341      *
342      * @returns The current average value or `Radio::kInvalidRssi` if no average is available.
343      *
344      */
GetAverageRss(void) const345     int8_t GetAverageRss(void) const { return mRssAverager.GetAverage(); }
346 
347     /**
348      * Returns an encoded version of current average signal strength value. The encoded value is the
349      * average multiplied by a precision factor (currently -8).
350      *
351      * @returns The current average multiplied by precision factor or zero if no average is available.
352      *
353      */
GetAverageRssRaw(void) const354     uint16_t GetAverageRssRaw(void) const { return mRssAverager.GetRaw(); }
355 
356     /**
357      * Converts the link quality info to info/debug human-readable string.
358      *
359      * @returns An `InfoString` representing the link quality info.
360      *
361      */
362     InfoString ToInfoString(void) const;
363 
364     /**
365      * Returns the link margin. The link margin is calculated using the link's current average received
366      * signal strength (RSS) and average noise floor.
367      *
368      * @returns Link margin derived from average received signal strength and average noise floor.
369      *
370      */
371     uint8_t GetLinkMargin(void) const;
372 
373     /**
374      * Returns the current one-way link quality value. The link quality value is a number 0-3.
375      *
376      * The link quality is calculated by comparing the current link margin with a set of thresholds (per Thread spec).
377      * More specifically, link margin > 20 dB gives link quality 3, link margin > 10 dB gives link quality 2,
378      * link margin > 2 dB gives link quality 1, and link margin below or equal to 2 dB yields link quality of 0.
379      *
380      * In order to ensure that a link margin near the boundary of two different link quality values does not cause
381      * frequent changes, a hysteresis of 2 dB is applied when determining the link quality. For example, the average
382      * link margin must be at least 12 dB to change a quality 1 link to a quality 2 link.
383      *
384      * @returns The current link quality value (value 0-3 as per Thread specification).
385      *
386      */
GetLinkQuality(void) const387     LinkQuality GetLinkQuality(void) const { return mLinkQuality; }
388 
389     /**
390      * Returns the most recent RSS value.
391      *
392      * @returns The most recent RSS
393      *
394      */
GetLastRss(void) const395     int8_t GetLastRss(void) const { return mLastRss; }
396 
397     /**
398      * Adds a MAC frame transmission status (success/failure) and updates the frame tx error rate.
399      *
400      * @param[in]  aTxStatus   Success/Failure of MAC frame transmission (`true` -> success, `false` -> failure).
401      *
402      */
AddFrameTxStatus(bool aTxStatus)403     void AddFrameTxStatus(bool aTxStatus)
404     {
405         mFrameErrorRate.AddSample(aTxStatus, OPENTHREAD_CONFIG_FRAME_TX_ERR_RATE_AVERAGING_WINDOW);
406     }
407 
408     /**
409      * Adds a message transmission status (success/failure) and updates the message error rate.
410      *
411      * @param[in]  aTxStatus   Success/Failure of message (`true` -> success, `false` -> message tx failed).
412      *                         A larger (IPv6) message may be fragmented and sent as multiple MAC frames. The message
413      *                         transmission is considered a failure, if any of its fragments fail after all MAC retry
414      *                         attempts.
415      *
416      */
AddMessageTxStatus(bool aTxStatus)417     void AddMessageTxStatus(bool aTxStatus)
418     {
419         mMessageErrorRate.AddSample(aTxStatus, OPENTHREAD_CONFIG_IPV6_TX_ERR_RATE_AVERAGING_WINDOW);
420     }
421 
422     /**
423      * Returns the MAC frame transmission error rate for the link.
424      *
425      * The rate is maintained over a window of (roughly) last `OPENTHREAD_CONFIG_FRAME_TX_ERR_RATE_AVERAGING_WINDOW`
426      * frame transmissions.
427      *
428      * @returns The error rate with maximum value `0xffff` corresponding to 100% failure rate.
429      *
430      */
GetFrameErrorRate(void) const431     uint16_t GetFrameErrorRate(void) const { return mFrameErrorRate.GetFailureRate(); }
432 
433     /**
434      * Returns the message error rate for the link.
435      *
436      * The rate is maintained over a window of (roughly) last `OPENTHREAD_CONFIG_IPV6_TX_ERR_RATE_AVERAGING_WINDOW`
437      * (IPv6) messages.
438      *
439      * Note that a larger (IPv6) message can be fragmented and sent as multiple MAC frames. The message transmission is
440      * considered a failure, if any of its fragments fail after all MAC retry attempts.
441      *
442      * @returns The error rate with maximum value `0xffff` corresponding to 100% failure rate.
443      *
444      */
GetMessageErrorRate(void) const445     uint16_t GetMessageErrorRate(void) const { return mMessageErrorRate.GetFailureRate(); }
446 
447 private:
448     // Constants for obtaining link quality from link margin:
449 
450     static constexpr uint8_t kThreshold3          = 20; // Link margin threshold for quality 3 link.
451     static constexpr uint8_t kThreshold2          = 10; // Link margin threshold for quality 2 link.
452     static constexpr uint8_t kThreshold1          = 2;  // Link margin threshold for quality 1 link.
453     static constexpr uint8_t kHysteresisThreshold = 2;  // Link margin hysteresis threshold.
454 
455     static constexpr int8_t kLinkQuality3LinkMargin = 50; // link margin for Link Quality 3 (21 - 255)
456     static constexpr int8_t kLinkQuality2LinkMargin = 15; // link margin for Link Quality 3 (21 - 255)
457     static constexpr int8_t kLinkQuality1LinkMargin = 5;  // link margin for Link Quality 3 (21 - 255)
458     static constexpr int8_t kLinkQuality0LinkMargin = 0;  // link margin for Link Quality 3 (21 - 255)
459 
460     static constexpr uint8_t kNoLinkQuality = 0xff; // Indicate that there is no previous/last link quality.
461 
SetLinkQuality(LinkQuality aLinkQuality)462     void SetLinkQuality(LinkQuality aLinkQuality) { mLinkQuality = aLinkQuality; }
463 
464     static LinkQuality CalculateLinkQuality(uint8_t aLinkMargin, uint8_t aLastLinkQuality);
465 
466     RssAverager mRssAverager;
467     LinkQuality mLinkQuality;
468     int8_t      mLastRss;
469 
470     SuccessRateTracker mFrameErrorRate;
471     SuccessRateTracker mMessageErrorRate;
472 };
473 
474 /**
475  * @}
476  */
477 
478 } // namespace ot
479 
480 #endif // LINK_QUALITY_HPP_
481