1 /*
2 * Copyright (c) 2023, 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 the CLI interpreter for Link Metrics function.
32 */
33
34 #include "cli_link_metrics.hpp"
35
36 #include <openthread/link_metrics.h>
37
38 #include "cli/cli.hpp"
39 #include "cli/cli_utils.hpp"
40 #include "common/code_utils.hpp"
41
42 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
43
44 namespace ot {
45 namespace Cli {
46
LinkMetrics(otInstance * aInstance,OutputImplementer & aOutputImplementer)47 LinkMetrics::LinkMetrics(otInstance *aInstance, OutputImplementer &aOutputImplementer)
48 : Utils(aInstance, aOutputImplementer)
49 , mQuerySync(false)
50 , mConfigForwardTrackingSeriesSync(false)
51 , mConfigEnhAckProbingSync(false)
52 {
53 }
54
Process(Arg aArgs[])55 template <> otError LinkMetrics::Process<Cmd("query")>(Arg aArgs[])
56 {
57 OT_UNUSED_VARIABLE(aArgs);
58 OutputLine("The command \"linkmetrics query\" has been replaced by the command \"linkmetrics request\".");
59 return OT_ERROR_INVALID_COMMAND;
60 }
61
Process(Arg aArgs[])62 template <> otError LinkMetrics::Process<Cmd("request")>(Arg aArgs[])
63 {
64 otError error = OT_ERROR_NONE;
65 otIp6Address address;
66 bool sync = true;
67 bool isSingle;
68 uint8_t seriesId;
69 otLinkMetrics linkMetrics;
70
71 if (aArgs[0] == "async")
72 {
73 sync = false;
74 aArgs++;
75 }
76
77 SuccessOrExit(error = aArgs[0].ParseAsIp6Address(address));
78
79 /**
80 * @cli linkmetrics request single
81 * @code
82 * linkmetrics request fe80:0:0:0:3092:f334:1455:1ad2 single qmr
83 * Received Link Metrics Report from: fe80:0:0:0:3092:f334:1455:1ad2
84 * - LQI: 76 (Exponential Moving Average)
85 * - Margin: 82 (dB) (Exponential Moving Average)
86 * - RSSI: -18 (dBm) (Exponential Moving Average)
87 * Done
88 * @endcode
89 * @cparam linkmetrics request [@ca{async}] @ca{peer-ipaddr} single [@ca{pqmr}]
90 * - `async`: Use the non-blocking mode.
91 * - `peer-ipaddr`: Peer address.
92 * - [`p`, `q`, `m`, and `r`] map to #otLinkMetrics.
93 * - `p`: Layer 2 Number of PDUs received.
94 * - `q`: Layer 2 LQI.
95 * - `m`: Link Margin.
96 * - `r`: RSSI.
97 * @par
98 * Perform a Link Metrics query (Single Probe).
99 * @sa otLinkMetricsQuery
100 */
101 if (aArgs[1] == "single")
102 {
103 isSingle = true;
104 SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[2]));
105 }
106 /**
107 * @cli linkmetrics request forward
108 * @code
109 * linkmetrics request fe80:0:0:0:3092:f334:1455:1ad2 forward 1
110 * Received Link Metrics Report from: fe80:0:0:0:3092:f334:1455:1ad2
111 * - PDU Counter: 2 (Count/Summation)
112 * - LQI: 76 (Exponential Moving Average)
113 * - Margin: 82 (dB) (Exponential Moving Average)
114 * - RSSI: -18 (dBm) (Exponential Moving Average)
115 * Done
116 * @endcode
117 * @cparam linkmetrics query [@ca{async}] @ca{peer-ipaddr} forward @ca{series-id}
118 * - `async`: Use the non-blocking mode.
119 * - `peer-ipaddr`: Peer address.
120 * - `series-id`: The Series ID.
121 * @par
122 * Perform a Link Metrics query (Forward Tracking Series).
123 * @sa otLinkMetricsQuery
124 */
125 else if (aArgs[1] == "forward")
126 {
127 isSingle = false;
128 SuccessOrExit(error = aArgs[2].ParseAsUint8(seriesId));
129 }
130 else
131 {
132 ExitNow(error = OT_ERROR_INVALID_ARGS);
133 }
134
135 SuccessOrExit(error = otLinkMetricsQuery(GetInstancePtr(), &address, isSingle ? 0 : seriesId,
136 isSingle ? &linkMetrics : nullptr, HandleLinkMetricsReport, this));
137
138 if (sync)
139 {
140 mQuerySync = true;
141 error = OT_ERROR_PENDING;
142 }
143
144 exit:
145 return error;
146 }
147
Process(Arg aArgs[])148 template <> otError LinkMetrics::Process<Cmd("mgmt")>(Arg aArgs[])
149 {
150 OT_UNUSED_VARIABLE(aArgs);
151 OutputLine("The command \"linkmetrics mgmt\" has been replaced by the command \"linkmetrics config\".");
152 return OT_ERROR_INVALID_COMMAND;
153 }
154
Process(Arg aArgs[])155 template <> otError LinkMetrics::Process<Cmd("config")>(Arg aArgs[])
156 {
157 otError error;
158 otIp6Address address;
159 otLinkMetricsSeriesFlags seriesFlags;
160 bool sync = true;
161 bool clear = false;
162
163 if (aArgs[0] == "async")
164 {
165 sync = false;
166 aArgs++;
167 }
168
169 SuccessOrExit(error = aArgs[0].ParseAsIp6Address(address));
170
171 ClearAllBytes(seriesFlags);
172
173 /**
174 * @cli linkmetrics config forward
175 * @code
176 * linkmetrics config fe80:0:0:0:3092:f334:1455:1ad2 forward 1 dra pqmr
177 * Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
178 * Status: SUCCESS
179 * Done
180 * @endcode
181 * @cparam linkmetrics config [@ca{async}] @ca{peer-ipaddr} forward @ca{series-id} [@ca{ldraX}][@ca{pqmr}]
182 * - `async`: Use the non-blocking mode.
183 * - `peer-ipaddr`: Peer address.
184 * - `series-id`: The Series ID.
185 * - [`l`, `d`, `r`, and `a`] map to #otLinkMetricsSeriesFlags. `X` represents none of the
186 * `otLinkMetricsSeriesFlags`, and stops the accounting and removes the series.
187 * - `l`: MLE Link Probe.
188 * - `d`: MAC Data.
189 * - `r`: MAC Data Request.
190 * - `a`: MAC Ack.
191 * - `X`: Can only be used without any other flags.
192 * - [`p`, `q`, `m`, and `r`] map to #otLinkMetricsValues.
193 * - `p`: Layer 2 Number of PDUs received.
194 * - `q`: Layer 2 LQI.
195 * - `m`: Link Margin.
196 * - `r`: RSSI.
197 * @par api_copy
198 * #otLinkMetricsConfigForwardTrackingSeries
199 */
200 if (aArgs[1] == "forward")
201 {
202 uint8_t seriesId;
203 otLinkMetrics linkMetrics;
204
205 ClearAllBytes(linkMetrics);
206 SuccessOrExit(error = aArgs[2].ParseAsUint8(seriesId));
207 VerifyOrExit(!aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
208
209 for (const char *arg = aArgs[3].GetCString(); *arg != '\0'; arg++)
210 {
211 switch (*arg)
212 {
213 case 'l':
214 seriesFlags.mLinkProbe = true;
215 break;
216
217 case 'd':
218 seriesFlags.mMacData = true;
219 break;
220
221 case 'r':
222 seriesFlags.mMacDataRequest = true;
223 break;
224
225 case 'a':
226 seriesFlags.mMacAck = true;
227 break;
228
229 case 'X':
230 VerifyOrExit(arg == aArgs[3].GetCString() && *(arg + 1) == '\0' && aArgs[4].IsEmpty(),
231 error = OT_ERROR_INVALID_ARGS); // Ensure the flags only contain 'X'
232 clear = true;
233 break;
234
235 default:
236 ExitNow(error = OT_ERROR_INVALID_ARGS);
237 }
238 }
239
240 if (!clear)
241 {
242 SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[4]));
243 VerifyOrExit(aArgs[5].IsEmpty(), error = OT_ERROR_INVALID_ARGS);
244 }
245
246 SuccessOrExit(error = otLinkMetricsConfigForwardTrackingSeries(
247 GetInstancePtr(), &address, seriesId, seriesFlags, clear ? nullptr : &linkMetrics,
248 &LinkMetrics::HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse, this));
249
250 if (sync)
251 {
252 mConfigForwardTrackingSeriesSync = true;
253 error = OT_ERROR_PENDING;
254 }
255 }
256 else if (aArgs[1] == "enhanced-ack")
257 {
258 otLinkMetricsEnhAckFlags enhAckFlags;
259 otLinkMetrics linkMetrics;
260 otLinkMetrics *pLinkMetrics = &linkMetrics;
261
262 /**
263 * @cli linkmetrics config enhanced-ack clear
264 * @code
265 * linkmetrics config fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack clear
266 * Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
267 * Status: Success
268 * Done
269 * @endcode
270 * @cparam linkmetrics config [@ca{async}] @ca{peer-ipaddr} enhanced-ack clear
271 * - `async`: Use the non-blocking mode.
272 * - `peer-ipaddr` should be the Link Local address of the neighboring device.
273 * @par
274 * Sends a Link Metrics Management Request to clear an Enhanced-ACK Based Probing.
275 * @sa otLinkMetricsConfigEnhAckProbing
276 */
277 if (aArgs[2] == "clear")
278 {
279 enhAckFlags = OT_LINK_METRICS_ENH_ACK_CLEAR;
280 pLinkMetrics = nullptr;
281 }
282 /**
283 * @cli linkmetrics config enhanced-ack register
284 * @code
285 * linkmetrics config fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack register qm
286 * Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
287 * Status: Success
288 * Done
289 * @endcode
290 * @code
291 * > linkmetrics config fe80:0:0:0:3092:f334:1455:1ad2 enhanced-ack register qm r
292 * Received Link Metrics Management Response from: fe80:0:0:0:3092:f334:1455:1ad2
293 * Status: Cannot support new series
294 * Done
295 * @endcode
296 * @cparam linkmetrics config [@ca{async}] @ca{peer-ipaddr} enhanced-ack register [@ca{qmr}][@ca{r}]
297 * - `async`: Use the non-blocking mode.
298 * - [`q`, `m`, and `r`] map to #otLinkMetricsValues. Per spec 4.11.3.4.4.6, you can
299 * only use a maximum of two options at once, for example `q`, or `qm`.
300 * - `q`: Layer 2 LQI.
301 * - `m`: Link Margin.
302 * - `r`: RSSI.
303 * @par
304 * The additional `r` is optional and only used for reference devices. When this option
305 * is specified, Type/Average Enum of each Type Id Flags is set to reserved. This is
306 * used to verify that the Probing Subject correctly handles invalid Type Id Flags, and
307 * only available when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled.
308 * @par
309 * Sends a Link Metrics Management Request to register an Enhanced-ACK Based Probing.
310 * @sa otLinkMetricsConfigEnhAckProbing
311 */
312 else if (aArgs[2] == "register")
313 {
314 enhAckFlags = OT_LINK_METRICS_ENH_ACK_REGISTER;
315 SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[3]));
316 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
317 if (aArgs[4] == "r")
318 {
319 linkMetrics.mReserved = true;
320 }
321 #endif
322 }
323 else
324 {
325 ExitNow(error = OT_ERROR_INVALID_ARGS);
326 }
327
328 SuccessOrExit(
329 error = otLinkMetricsConfigEnhAckProbing(GetInstancePtr(), &address, enhAckFlags, pLinkMetrics,
330 &LinkMetrics::HandleLinkMetricsConfigEnhAckProbingMgmtResponse,
331 this, &LinkMetrics::HandleLinkMetricsEnhAckProbingIe, this));
332
333 if (sync)
334 {
335 mConfigEnhAckProbingSync = true;
336 error = OT_ERROR_PENDING;
337 }
338 }
339 else
340 {
341 error = OT_ERROR_INVALID_ARGS;
342 }
343
344 exit:
345 return error;
346 }
347
Process(Arg aArgs[])348 template <> otError LinkMetrics::Process<Cmd("probe")>(Arg aArgs[])
349 {
350 /**
351 * @cli linkmetrics probe
352 * @code
353 * linkmetrics probe fe80:0:0:0:3092:f334:1455:1ad2 1 10
354 * Done
355 * @endcode
356 * @cparam linkmetrics probe @ca{peer-ipaddr} @ca{series-id} @ca{length}
357 * - `peer-ipaddr`: Peer address.
358 * - `series-id`: The Series ID for which this Probe message targets.
359 * - `length`: The length of the Probe message. A valid range is [0, 64].
360 * @par api_copy
361 * #otLinkMetricsSendLinkProbe
362 */
363 otError error = OT_ERROR_NONE;
364
365 otIp6Address address;
366 uint8_t seriesId;
367 uint8_t length;
368
369 SuccessOrExit(error = aArgs[0].ParseAsIp6Address(address));
370 SuccessOrExit(error = aArgs[1].ParseAsUint8(seriesId));
371 SuccessOrExit(error = aArgs[2].ParseAsUint8(length));
372
373 error = otLinkMetricsSendLinkProbe(GetInstancePtr(), &address, seriesId, length);
374 exit:
375 return error;
376 }
377
Process(Arg aArgs[])378 otError LinkMetrics::Process(Arg aArgs[])
379 {
380 #define CmdEntry(aCommandString) \
381 { \
382 aCommandString, &LinkMetrics::Process<Cmd(aCommandString)> \
383 }
384
385 static constexpr Command kCommands[] = {
386 CmdEntry("config"), CmdEntry("mgmt"), CmdEntry("probe"), CmdEntry("query"), CmdEntry("request"),
387 };
388
389 static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted");
390
391 otError error = OT_ERROR_INVALID_COMMAND;
392 const Command *command;
393
394 if (aArgs[0].IsEmpty() || (aArgs[0] == "help"))
395 {
396 OutputCommandTable(kCommands);
397 ExitNow(error = aArgs[0].IsEmpty() ? error : OT_ERROR_NONE);
398 }
399
400 command = BinarySearch::Find(aArgs[0].GetCString(), kCommands);
401 VerifyOrExit(command != nullptr);
402
403 error = (this->*command->mHandler)(aArgs + 1);
404
405 exit:
406 return error;
407 }
408
ParseLinkMetricsFlags(otLinkMetrics & aLinkMetrics,const Arg & aFlags)409 otError LinkMetrics::ParseLinkMetricsFlags(otLinkMetrics &aLinkMetrics, const Arg &aFlags)
410 {
411 otError error = OT_ERROR_NONE;
412
413 VerifyOrExit(!aFlags.IsEmpty(), error = OT_ERROR_INVALID_ARGS);
414
415 ClearAllBytes(aLinkMetrics);
416
417 for (const char *arg = aFlags.GetCString(); *arg != '\0'; arg++)
418 {
419 switch (*arg)
420 {
421 case 'p':
422 aLinkMetrics.mPduCount = true;
423 break;
424
425 case 'q':
426 aLinkMetrics.mLqi = true;
427 break;
428
429 case 'm':
430 aLinkMetrics.mLinkMargin = true;
431 break;
432
433 case 'r':
434 aLinkMetrics.mRssi = true;
435 break;
436
437 default:
438 ExitNow(error = OT_ERROR_INVALID_ARGS);
439 }
440 }
441
442 exit:
443 return error;
444 }
445
HandleLinkMetricsReport(const otIp6Address * aAddress,const otLinkMetricsValues * aMetricsValues,otLinkMetricsStatus aStatus,void * aContext)446 void LinkMetrics::HandleLinkMetricsReport(const otIp6Address *aAddress,
447 const otLinkMetricsValues *aMetricsValues,
448 otLinkMetricsStatus aStatus,
449 void *aContext)
450 {
451 static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsReport(aAddress, aMetricsValues, aStatus);
452 }
453
PrintLinkMetricsValue(const otLinkMetricsValues * aMetricsValues)454 void LinkMetrics::PrintLinkMetricsValue(const otLinkMetricsValues *aMetricsValues)
455 {
456 static const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)";
457
458 if (aMetricsValues->mMetrics.mPduCount)
459 {
460 OutputLine(" - PDU Counter: %lu (Count/Summation)", ToUlong(aMetricsValues->mPduCountValue));
461 }
462
463 if (aMetricsValues->mMetrics.mLqi)
464 {
465 OutputLine(" - LQI: %u %s", aMetricsValues->mLqiValue, kLinkMetricsTypeAverage);
466 }
467
468 if (aMetricsValues->mMetrics.mLinkMargin)
469 {
470 OutputLine(" - Margin: %u (dB) %s", aMetricsValues->mLinkMarginValue, kLinkMetricsTypeAverage);
471 }
472
473 if (aMetricsValues->mMetrics.mRssi)
474 {
475 OutputLine(" - RSSI: %d (dBm) %s", aMetricsValues->mRssiValue, kLinkMetricsTypeAverage);
476 }
477 }
478
HandleLinkMetricsReport(const otIp6Address * aAddress,const otLinkMetricsValues * aMetricsValues,otLinkMetricsStatus aStatus)479 void LinkMetrics::HandleLinkMetricsReport(const otIp6Address *aAddress,
480 const otLinkMetricsValues *aMetricsValues,
481 otLinkMetricsStatus aStatus)
482 {
483 OutputFormat("Received Link Metrics Report from: ");
484 OutputIp6AddressLine(*aAddress);
485
486 if (aMetricsValues != nullptr)
487 {
488 PrintLinkMetricsValue(aMetricsValues);
489 }
490 else
491 {
492 OutputLine("Link Metrics Report, status: %s", LinkMetricsStatusToStr(aStatus));
493 }
494
495 if (mQuerySync)
496 {
497 mQuerySync = false;
498 OutputResult(OT_ERROR_NONE);
499 }
500 }
501
HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus,void * aContext)502 void LinkMetrics::HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(const otIp6Address *aAddress,
503 otLinkMetricsStatus aStatus,
504 void *aContext)
505 {
506 static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(aAddress, aStatus);
507 }
508
HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus)509 void LinkMetrics::HandleLinkMetricsConfigForwardTrackingSeriesMgmtResponse(const otIp6Address *aAddress,
510 otLinkMetricsStatus aStatus)
511 {
512 HandleLinkMetricsMgmtResponse(aAddress, aStatus);
513
514 if (mConfigForwardTrackingSeriesSync)
515 {
516 mConfigForwardTrackingSeriesSync = false;
517 OutputResult(OT_ERROR_NONE);
518 }
519 }
520
HandleLinkMetricsConfigEnhAckProbingMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus,void * aContext)521 void LinkMetrics::HandleLinkMetricsConfigEnhAckProbingMgmtResponse(const otIp6Address *aAddress,
522 otLinkMetricsStatus aStatus,
523 void *aContext)
524 {
525 static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsConfigEnhAckProbingMgmtResponse(aAddress, aStatus);
526 }
527
HandleLinkMetricsConfigEnhAckProbingMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus)528 void LinkMetrics::HandleLinkMetricsConfigEnhAckProbingMgmtResponse(const otIp6Address *aAddress,
529 otLinkMetricsStatus aStatus)
530 {
531 HandleLinkMetricsMgmtResponse(aAddress, aStatus);
532
533 if (mConfigEnhAckProbingSync)
534 {
535 mConfigEnhAckProbingSync = false;
536 OutputResult(OT_ERROR_NONE);
537 }
538 }
539
HandleLinkMetricsMgmtResponse(const otIp6Address * aAddress,otLinkMetricsStatus aStatus)540 void LinkMetrics::HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus)
541 {
542 OutputFormat("Received Link Metrics Management Response from: ");
543 OutputIp6AddressLine(*aAddress);
544
545 OutputLine("Status: %s", LinkMetricsStatusToStr(aStatus));
546 }
547
HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress,const otExtAddress * aExtAddress,const otLinkMetricsValues * aMetricsValues,void * aContext)548 void LinkMetrics::HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress,
549 const otExtAddress *aExtAddress,
550 const otLinkMetricsValues *aMetricsValues,
551 void *aContext)
552 {
553 static_cast<LinkMetrics *>(aContext)->HandleLinkMetricsEnhAckProbingIe(aShortAddress, aExtAddress, aMetricsValues);
554 }
555
HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress,const otExtAddress * aExtAddress,const otLinkMetricsValues * aMetricsValues)556 void LinkMetrics::HandleLinkMetricsEnhAckProbingIe(otShortAddress aShortAddress,
557 const otExtAddress *aExtAddress,
558 const otLinkMetricsValues *aMetricsValues)
559 {
560 OutputFormat("Received Link Metrics data in Enh Ack from neighbor, short address:0x%02x , extended address:",
561 aShortAddress);
562 OutputExtAddressLine(*aExtAddress);
563
564 if (aMetricsValues != nullptr)
565 {
566 PrintLinkMetricsValue(aMetricsValues);
567 }
568 }
569
LinkMetricsStatusToStr(otLinkMetricsStatus aStatus)570 const char *LinkMetrics::LinkMetricsStatusToStr(otLinkMetricsStatus aStatus)
571 {
572 static const char *const kStatusStrings[] = {
573 "Success", // (0) OT_LINK_METRICS_STATUS_SUCCESS
574 "Cannot support new series", // (1) OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES
575 "Series ID already registered", // (2) OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED
576 "Series ID not recognized", // (3) OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED
577 "No matching series ID", // (4) OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED
578 };
579
580 const char *str = "Unknown error";
581
582 static_assert(0 == OT_LINK_METRICS_STATUS_SUCCESS, "STATUS_SUCCESS is incorrect");
583 static_assert(1 == OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES, "CANNOT_SUPPORT_NEW_SERIES is incorrect");
584 static_assert(2 == OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED, "SERIESID_ALREADY_REGISTERED is incorrect");
585 static_assert(3 == OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED, "SERIESID_NOT_RECOGNIZED is incorrect");
586 static_assert(4 == OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED, "NO_MATCHING_FRAMES_RECEIVED is incorrect");
587
588 if (aStatus < OT_ARRAY_LENGTH(kStatusStrings))
589 {
590 str = kStatusStrings[aStatus];
591 }
592 else if (aStatus == OT_LINK_METRICS_STATUS_OTHER_ERROR)
593 {
594 str = "Other error";
595 }
596
597 return str;
598 }
599
OutputResult(otError aError)600 void LinkMetrics::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
601
602 } // namespace Cli
603 } // namespace ot
604
605 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
606