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