diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index df9d6af10..59429be24 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -135,6 +135,9 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE , mLocateInProgress(false) #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE + , mLinkMetricsQueryInProgress(false) +#endif #endif // OPENTHREAD_FTD || OPENTHREAD_MTD { #if OPENTHREAD_FTD @@ -3467,12 +3470,11 @@ void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress, void Interpreter::PrintLinkMetricsValue(const otLinkMetricsValues *aMetricsValues) { - const char kLinkMetricsTypeCount[] = "(Count/Summation)"; - const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)"; + static const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)"; if (aMetricsValues->mMetrics.mPduCount) { - OutputLine(" - PDU Counter: %d %s", aMetricsValues->mPduCountValue, kLinkMetricsTypeCount); + OutputLine(" - PDU Counter: %d (Count/Summation)", aMetricsValues->mPduCountValue); } if (aMetricsValues->mMetrics.mLqi) @@ -3506,6 +3508,12 @@ void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress, { OutputLine("Link Metrics Report, status: %s", LinkMetricsStatusToStr(aStatus)); } + + if (mLinkMetricsQueryInProgress) + { + mLinkMetricsQueryInProgress = false; + OutputResult(OT_ERROR_NONE); + } } void Interpreter::HandleLinkMetricsMgmtResponse(const otIp6Address *aAddress, uint8_t aStatus, void *aContext) @@ -3580,28 +3588,37 @@ template <> otError Interpreter::Process(Arg aArgs[]) if (aArgs[0] == "query") { otIp6Address address; + bool isSingle; + bool blocking; + uint8_t seriesId; otLinkMetrics linkMetrics; SuccessOrExit(error = aArgs[1].ParseAsIp6Address(address)); if (aArgs[2] == "single") { - VerifyOrExit(!aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + isSingle = true; SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[3])); - error = otLinkMetricsQuery(GetInstancePtr(), &address, /* aSeriesId */ 0, &linkMetrics, - &Interpreter::HandleLinkMetricsReport, this); } else if (aArgs[2] == "forward") { - uint8_t seriesId; - + isSingle = false; SuccessOrExit(error = aArgs[3].ParseAsUint8(seriesId)); - error = otLinkMetricsQuery(GetInstancePtr(), &address, seriesId, nullptr, - &Interpreter::HandleLinkMetricsReport, this); } else { - error = OT_ERROR_INVALID_ARGS; + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + + blocking = (aArgs[4] == "block"); + + SuccessOrExit(error = otLinkMetricsQuery(GetInstancePtr(), &address, isSingle ? 0 : seriesId, + isSingle ? &linkMetrics : nullptr, HandleLinkMetricsReport, this)); + + if (blocking) + { + mLinkMetricsQueryInProgress = true; + error = OT_ERROR_PENDING; } } else if (aArgs[0] == "mgmt") @@ -3629,6 +3646,8 @@ otError Interpreter::ParseLinkMetricsFlags(otLinkMetrics &aLinkMetrics, const Ar { otError error = OT_ERROR_NONE; + VerifyOrExit(!aFlags.IsEmpty(), error = OT_ERROR_INVALID_ARGS); + memset(&aLinkMetrics, 0, sizeof(aLinkMetrics)); for (const char *arg = aFlags.GetCString(); *arg != '\0'; arg++) @@ -3713,8 +3732,8 @@ otError Interpreter::ProcessLinkMetricsMgmt(Arg aArgs[]) if (!clear) { - VerifyOrExit(!aArgs[4].IsEmpty() && aArgs[5].IsEmpty(), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[4])); + VerifyOrExit(aArgs[5].IsEmpty(), error = OT_ERROR_INVALID_ARGS); } error = otLinkMetricsConfigForwardTrackingSeries(GetInstancePtr(), &address, seriesId, seriesFlags, @@ -3735,7 +3754,6 @@ otError Interpreter::ProcessLinkMetricsMgmt(Arg aArgs[]) else if (aArgs[2] == "register") { enhAckFlags = OT_LINK_METRICS_ENH_ACK_REGISTER; - VerifyOrExit(!aArgs[3].IsEmpty(), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = ParseLinkMetricsFlags(linkMetrics, aArgs[3])); #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE if (aArgs[4] == "r") diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index f4aadbbe8..5b25f4f22 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -547,6 +547,10 @@ private: #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE bool mLocateInProgress : 1; #endif + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE + bool mLinkMetricsQueryInProgress : 1; +#endif }; // Specializations of `FormatStringFor()` diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index ddc960304..8a477f9c4 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -2861,15 +2861,38 @@ class NodeImpl: return router_table - def link_metrics_query_single_probe(self, dst_addr: str, linkmetrics_flags: str): - cmd = 'linkmetrics query %s single %s' % (dst_addr, linkmetrics_flags) + def link_metrics_query_single_probe(self, dst_addr: str, linkmetrics_flags: str, block: str = ""): + cmd = 'linkmetrics query %s single %s %s' % (dst_addr, linkmetrics_flags, block) self.send_command(cmd) - self._expect_done() + self.simulator.go(5) + return self._parse_linkmetrics_query_result(self._expect_command_output()) - def link_metrics_query_forward_tracking_series(self, dst_addr: str, series_id: int): - cmd = 'linkmetrics query %s forward %d' % (dst_addr, series_id) + def link_metrics_query_forward_tracking_series(self, dst_addr: str, series_id: int, block: str = ""): + cmd = 'linkmetrics query %s forward %d %s' % (dst_addr, series_id, block) self.send_command(cmd) - self._expect_done() + self.simulator.go(5) + return self._parse_linkmetrics_query_result(self._expect_command_output()) + + def _parse_linkmetrics_query_result(self, lines): + """Parse link metrics query result""" + + # Exmaple of command output: + # ['Received Link Metrics Report from: fe80:0:0:0:146e:a00:0:1', + # '- PDU Counter: 1 (Count/Summation)', + # '- LQI: 0 (Exponential Moving Average)', + # '- Margin: 80 (dB) (Exponential Moving Average)', + # '- RSSI: -20 (dBm) (Exponential Moving Average)'] + # + # Or 'Link Metrics Report, status: {status}' + + result = {} + for line in lines: + if line.startswith('- '): + k, v = line[2:].split(': ') + result[k] = v.split(' ')[0] + elif line.startswith('Link Metrics Report, status: '): + result['Status'] = line[29:] + return result def link_metrics_mgmt_req_enhanced_ack_based_probing(self, dst_addr: str, diff --git a/tests/scripts/thread-cert/v1_2_LowPower_7_2_01_ForwardTrackingSeries.py b/tests/scripts/thread-cert/v1_2_LowPower_7_2_01_ForwardTrackingSeries.py index b9543cf10..e46ab4936 100755 --- a/tests/scripts/thread-cert/v1_2_LowPower_7_2_01_ForwardTrackingSeries.py +++ b/tests/scripts/thread-cert/v1_2_LowPower_7_2_01_ForwardTrackingSeries.py @@ -111,9 +111,10 @@ class LowPower_7_2_01_ForwardTrackingSeries(thread_cert.TestCase): self.simulator.go(30) # Step 7 - SED_1 sends an MLE Data Request to retrieve aggregated Forward Series Results - self.nodes[SED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID) + result = self.nodes[SED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID, 'block') + self.assertIn("PDU Counter", result) - self.simulator.go(5) + #self.simulator.go(5) # Step 9 - SED_1 clears the Forward Tracking Series # Forward Series Flags = 0x00: @@ -144,9 +145,8 @@ class LowPower_7_2_01_ForwardTrackingSeries(thread_cert.TestCase): self.simulator.go(1) # Step 19 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series Results - self.nodes[SSED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2) - - self.simulator.go(5) + result = self.nodes[SSED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2, 'block') + self.assertIn("Margin", result) # Step 21 - SSED_1 clears the Forward Series Link Metrics # Forward Series Flags = 0x00: @@ -157,9 +157,9 @@ class LowPower_7_2_01_ForwardTrackingSeries(thread_cert.TestCase): self.simulator.go(5) # Step 23 - SSED_1 sends an MLE Data Request to retrieve aggregated Forward Series Results - self.nodes[SSED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2) - - self.simulator.go(5) + result = self.nodes[SSED_1].link_metrics_query_forward_tracking_series(leader_addr, SERIES_ID_2, 'block') + self.assertIn('Status', result) + self.assertEqual(result['Status'], 'Series ID not recognized') def verify(self, pv): pkts = pv.pkts diff --git a/tests/scripts/thread-cert/v1_2_test_single_probe.py b/tests/scripts/thread-cert/v1_2_test_single_probe.py index b929479c9..93a8f67fe 100755 --- a/tests/scripts/thread-cert/v1_2_test_single_probe.py +++ b/tests/scripts/thread-cert/v1_2_test_single_probe.py @@ -70,40 +70,48 @@ class SSED_SingleProbe(thread_cert.TestCase): leader_messages = self.simulator.get_messages_sent_by(LEADER) # SSED_1 sends a Single Probe Link Metrics for L2 PDU count using MLE Data Request - self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'p') - self.simulator.go(5) + result = self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'p', 'block') + self.assertIn('PDU Counter', result) + self.assertEqual(len(result), 1) leader_messages = self.simulator.get_messages_sent_by(LEADER) msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE) msg.assertMleMessageContainsTlv(mle.LinkMetricsReport) # SSED_1 sends a Single Probe Link Metrics for L2 LQI using MLE Data Request - self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'q') - self.simulator.go(5) + result = self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'q', 'block') + self.assertIn('LQI', result) + self.assertEqual(len(result), 1) leader_messages = self.simulator.get_messages_sent_by(LEADER) msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE) msg.assertMleMessageContainsTlv(mle.LinkMetricsReport) # SSED_1 sends a Single Probe Link Metrics for Link Margin using MLE Data Request - self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'm') - self.simulator.go(5) + result = self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'm', 'block') + self.assertIn('Margin', result) + self.assertEqual(len(result), 1) leader_messages = self.simulator.get_messages_sent_by(LEADER) msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE) msg.assertMleMessageContainsTlv(mle.LinkMetricsReport) # SSED_1 sends a Single Probe Link Metrics for Link Margin using MLE Data Request - self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'r') - self.simulator.go(5) + result = self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'r', 'block') + self.assertIn('RSSI', result) + self.assertEqual(len(result), 1) leader_messages = self.simulator.get_messages_sent_by(LEADER) msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE) msg.assertMleMessageContainsTlv(mle.LinkMetricsReport) # SSED_1 sends a Single Probe Link Metrics for all metrics using MLE Data Request - self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'pqmr') - self.simulator.go(5) + result = self.nodes[SSED_1].link_metrics_query_single_probe(leader_addr, 'pqmr', 'block') + self.assertIn('PDU Counter', result) + self.assertIn('LQI', result) + self.assertIn('Margin', result) + self.assertIn('RSSI', result) + self.assertEqual(len(result), 4) leader_messages = self.simulator.get_messages_sent_by(LEADER) msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE)