mirror of
https://github.com/espressif/openthread.git
synced 2026-08-20 01:19:51 +00:00
[low-power] implement link metrics - single probe (#5481)
This commit is the first part in link metrics. It implements single probe function. An overview of major changes: - Add link metrics apis which allow apps to send a single probe and set report callback. - Add cli commands for calling link metrics api and related README doc. - Add a new module link_metrics to implement the query process and handle the report. - Add related tlv types. - Add simple script test to run single probe process.
This commit is contained in:
@@ -164,6 +164,7 @@ LOCAL_SRC_FILES := \
|
||||
src/core/api/jam_detection_api.cpp \
|
||||
src/core/api/joiner_api.cpp \
|
||||
src/core/api/link_api.cpp \
|
||||
src/core/api/link_metrics_api.cpp \
|
||||
src/core/api/link_raw_api.cpp \
|
||||
src/core/api/logging_api.cpp \
|
||||
src/core/api/message_api.cpp \
|
||||
@@ -255,6 +256,7 @@ LOCAL_SRC_FILES := \
|
||||
src/core/thread/energy_scan_server.cpp \
|
||||
src/core/thread/indirect_sender.cpp \
|
||||
src/core/thread/key_manager.cpp \
|
||||
src/core/thread/link_metrics.cpp \
|
||||
src/core/thread/link_quality.cpp \
|
||||
src/core/thread/lowpan.cpp \
|
||||
src/core/thread/mesh_forwarder.cpp \
|
||||
|
||||
@@ -177,6 +177,11 @@ if(OT_LINK_RAW)
|
||||
target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LINK_RAW_ENABLE=1")
|
||||
endif()
|
||||
|
||||
option(OT_LINK_METRICS "enable link metrics")
|
||||
if (OT_LINK_METRICS)
|
||||
target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE=1")
|
||||
endif()
|
||||
|
||||
option(OT_LOG_LEVEL_DYNAMIC "enable dynamic log level control")
|
||||
if(OT_LOG_LEVEL_DYNAMIC)
|
||||
target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE=1")
|
||||
|
||||
@@ -150,6 +150,9 @@ if (openthread_enable_core_config_args) {
|
||||
# Enable legacy network support
|
||||
openthread_config_legacy_enable = false
|
||||
|
||||
# Enable link metrics
|
||||
openthread_config_link_metrics_enable = false
|
||||
|
||||
# Enable link raw service
|
||||
openthread_config_link_raw_enable = false
|
||||
|
||||
|
||||
@@ -202,6 +202,10 @@ ifeq ($(LINK_RAW),1)
|
||||
COMMONCFLAGS += -DOPENTHREAD_CONFIG_LINK_RAW_ENABLE=1
|
||||
endif
|
||||
|
||||
ifeq ($(LINK_METRICS),1)
|
||||
COMMONCFLAGS += -DOPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE=1
|
||||
endif
|
||||
|
||||
ifneq ($(LOG_OUTPUT),)
|
||||
COMMONCFLAGS += -DOPENTHREAD_CONFIG_LOG_OUTPUT=OPENTHREAD_CONFIG_LOG_OUTPUT_$(LOG_OUTPUT)
|
||||
endif
|
||||
|
||||
@@ -60,6 +60,7 @@ openthread_headers = \
|
||||
openthread/jam_detection.h \
|
||||
openthread/joiner.h \
|
||||
openthread/link.h \
|
||||
openthread/link_metrics.h \
|
||||
openthread/link_raw.h \
|
||||
openthread/logging.h \
|
||||
openthread/message.h \
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 2020, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief
|
||||
* This file defines the OpenThread Link Metrics API.
|
||||
*/
|
||||
|
||||
#ifndef LINK_METRICS_H_
|
||||
#define LINK_METRICS_H_
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#include <openthread/ip6.h>
|
||||
#include <openthread/message.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @addtogroup linkmetrics Link Metrics
|
||||
*
|
||||
* @brief
|
||||
* This module includes functions that control Link Metrics protocol.
|
||||
*
|
||||
* @{
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This structure represents what metrics are specified to query.
|
||||
*
|
||||
*/
|
||||
typedef struct otLinkMetrics
|
||||
{
|
||||
bool mPduCount : 1;
|
||||
bool mLqi : 1;
|
||||
bool mLinkMargin : 1;
|
||||
bool mRssi : 1;
|
||||
} otLinkMetrics;
|
||||
|
||||
/*
|
||||
* This structure represents the result (value) for a Link Metrics query.
|
||||
*
|
||||
*/
|
||||
typedef struct otLinkMetricsValues
|
||||
{
|
||||
otLinkMetrics mMetrics; ///< Specifies which metrics values are present/included.
|
||||
|
||||
uint32_t mPduCountValue; ///< The value of Pdu Count.
|
||||
uint8_t mLqiValue; ///< The value LQI.
|
||||
uint8_t mLinkMarginValue; ///< The value of Link Margin.
|
||||
int8_t mRssiValue; ///< The value of Rssi.
|
||||
} otLinkMetricsValues;
|
||||
|
||||
/**
|
||||
* This function pointer is called when a Link Metrics report is received.
|
||||
*
|
||||
* @param[in] aSource A pointer to the source address.
|
||||
* @param[in] aMetricsValues A pointer to the Link Metrics values (the query result).
|
||||
* @param[in] aContext A pointer to application-specific context.
|
||||
*
|
||||
*/
|
||||
typedef void (*otLinkMetricsReportCallback)(const otIp6Address * aSource,
|
||||
const otLinkMetricsValues *aMetricsValues,
|
||||
void * aContext);
|
||||
|
||||
/**
|
||||
* This function sends an MLE Data Request to query Link Metrics.
|
||||
*
|
||||
* It could be either Single Probe or Forward Tracking Series.
|
||||
*
|
||||
* @param[in] aInstance A pointer to an OpenThread instance.
|
||||
* @param[in] aDestination A pointer to the destination address.
|
||||
* @param[in] aSeriesId The Series ID to query about, 0 for Single Probe.
|
||||
* @param[in] aLinkMetricsFlags A pointer to flags specifying what metrics to query.
|
||||
* @param[in] aCallback A pointer to a function that is called when Link Metrics report is received.
|
||||
* @param[in] aCallbackContext A pointer to application-specific context.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully sent a Link Metrics query message.
|
||||
* @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Data Request message.
|
||||
*
|
||||
*/
|
||||
otError otLinkMetricsQuery(otInstance * aInstance,
|
||||
const otIp6Address * aDestination,
|
||||
uint8_t aSeriesId,
|
||||
const otLinkMetrics * aLinkMetricsFlags,
|
||||
otLinkMetricsReportCallback aCallback,
|
||||
void * aCallbackContext);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // LINK_METRICS_H_
|
||||
@@ -67,6 +67,7 @@ build_simulation()
|
||||
|
||||
if [[ ${version} == "1.2" ]]; then
|
||||
options+=("-DOT_CSL_RECEIVER=ON")
|
||||
options+=("-DOT_LINK_METRICS=ON")
|
||||
fi
|
||||
|
||||
if [[ ${ot_extra_options[*]+x} ]]; then
|
||||
|
||||
@@ -57,6 +57,7 @@ Done
|
||||
- [leaderdata](#leaderdata)
|
||||
- [leaderpartitionid](#leaderpartitionid)
|
||||
- [leaderweight](#leaderweight)
|
||||
- [linkmetrics](#linkmetrics-query-ipaddr-single-pqmr)
|
||||
- [linkquality](#linkquality-extaddr)
|
||||
- [log](#log-filename-filename)
|
||||
- [mac](#mac-retries-direct)
|
||||
@@ -1066,6 +1067,27 @@ Set the Thread Leader Weight.
|
||||
Done
|
||||
```
|
||||
|
||||
### linkmetrics query \<ipaddr\> single [pqmr]
|
||||
|
||||
Perform a Link Metrics query (Single Probe).
|
||||
|
||||
- ipaddr: Peer address.
|
||||
- pqmr: This specifies what metrics to query.
|
||||
- p: Layer 2 Number of PDUs received.
|
||||
- q: Layer 2 LQI.
|
||||
- m: Link Margin.
|
||||
- r: RSSI.
|
||||
|
||||
```bash
|
||||
> linkmetrics query fe80:0:0:0:3092:f334:1455:1ad2 single qmr
|
||||
Done
|
||||
> Received Link Metrics Report from: fe80:0:0:0:3092:f334:1455:1ad2
|
||||
|
||||
- LQI: 76 (Exponential Moving Average)
|
||||
- Margin: 82 (dB) (Exponential Moving Average)
|
||||
- RSSI: -18 (dBm) (Exponential Moving Average)
|
||||
```
|
||||
|
||||
### linkquality \<extaddr\>
|
||||
|
||||
Get the link quality on the link to a given extended address.
|
||||
|
||||
+106
@@ -75,6 +75,9 @@
|
||||
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
|
||||
#include <openthread/backbone_router_ftd.h>
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
#include <openthread/link_metrics.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "cli_dataset.hpp"
|
||||
@@ -1899,6 +1902,109 @@ exit:
|
||||
}
|
||||
#endif // OPENTHREAD_FTD
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
void Interpreter::HandleLinkMetricsReport(const otIp6Address * aAddress,
|
||||
const otLinkMetricsValues *aMetricsValues,
|
||||
void * aContext)
|
||||
{
|
||||
static_cast<Interpreter *>(aContext)->HandleLinkMetricsReport(aAddress, aMetricsValues);
|
||||
}
|
||||
|
||||
void Interpreter::HandleLinkMetricsReport(const otIp6Address *aAddress, const otLinkMetricsValues *aMetricsValues)
|
||||
{
|
||||
const char kLinkMetricsTypeCount[] = "(Count/Summation)";
|
||||
const char kLinkMetricsTypeAverage[] = "(Exponential Moving Average)";
|
||||
|
||||
OutputFormat("Received Link Metrics Report from: ");
|
||||
OutputIp6Address(*aAddress);
|
||||
OutputLine("");
|
||||
|
||||
if (aMetricsValues->mMetrics.mPduCount)
|
||||
{
|
||||
OutputLine(" - PDU Counter: %d %s", aMetricsValues->mPduCountValue, kLinkMetricsTypeCount);
|
||||
}
|
||||
|
||||
if (aMetricsValues->mMetrics.mLqi)
|
||||
{
|
||||
OutputLine(" - LQI: %d %s", aMetricsValues->mLqiValue, kLinkMetricsTypeAverage);
|
||||
}
|
||||
|
||||
if (aMetricsValues->mMetrics.mLinkMargin)
|
||||
{
|
||||
OutputLine(" - Margin: %d (dB) %s", aMetricsValues->mLinkMarginValue, kLinkMetricsTypeAverage);
|
||||
}
|
||||
|
||||
if (aMetricsValues->mMetrics.mRssi)
|
||||
{
|
||||
OutputLine(" - RSSI: %d (dBm) %s", aMetricsValues->mRssiValue, kLinkMetricsTypeAverage);
|
||||
}
|
||||
}
|
||||
|
||||
otError Interpreter::ProcessLinkMetrics(uint8_t aArgsLength, char *aArgs[])
|
||||
{
|
||||
otError error = OT_ERROR_INVALID_COMMAND;
|
||||
|
||||
VerifyOrExit(aArgsLength >= 1, OT_NOOP);
|
||||
|
||||
if (strcmp(aArgs[0], "query") == 0)
|
||||
{
|
||||
error = ProcessLinkMetricsQuery(aArgsLength - 1, aArgs + 1);
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError Interpreter::ProcessLinkMetricsQuery(uint8_t aArgsLength, char *aArgs[])
|
||||
{
|
||||
otError error = OT_ERROR_INVALID_ARGS;
|
||||
otIp6Address address;
|
||||
otLinkMetrics linkMetrics;
|
||||
long seriesId = 0;
|
||||
|
||||
VerifyOrExit(aArgsLength >= 2, OT_NOOP);
|
||||
|
||||
SuccessOrExit(error = otIp6AddressFromString(aArgs[0], &address));
|
||||
|
||||
memset(&linkMetrics, 0, sizeof(otLinkMetrics));
|
||||
|
||||
if (strcmp(aArgs[1], "single") == 0)
|
||||
{
|
||||
VerifyOrExit(aArgsLength == 3, OT_NOOP);
|
||||
for (char *arg = aArgs[2]; *arg != '\0'; arg++)
|
||||
{
|
||||
switch (*arg)
|
||||
{
|
||||
case 'p':
|
||||
linkMetrics.mPduCount = 1;
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
linkMetrics.mLqi = 1;
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
linkMetrics.mLinkMargin = 1;
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
linkMetrics.mRssi = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
ExitNow(error = OT_ERROR_INVALID_ARGS);
|
||||
}
|
||||
}
|
||||
error = otLinkMetricsQuery(mInstance, &address, static_cast<uint8_t>(seriesId), &linkMetrics,
|
||||
&Interpreter::HandleLinkMetricsReport, this);
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
otError Interpreter::ProcessPskc(uint8_t aArgsLength, char *aArgs[])
|
||||
{
|
||||
|
||||
@@ -370,6 +370,10 @@ private:
|
||||
otError ProcessLeaderWeight(uint8_t aArgsLength, char *aArgs[]);
|
||||
#endif
|
||||
otError ProcessMasterKey(uint8_t aArgsLength, char *aArgs[]);
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
otError ProcessLinkMetrics(uint8_t aArgsLength, char *aArgs[]);
|
||||
otError ProcessLinkMetricsQuery(uint8_t aArgsLength, char *aArgs[]);
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE
|
||||
otError ProcessMlr(uint8_t aArgsLength, char *aArgs[]);
|
||||
|
||||
@@ -530,6 +534,14 @@ private:
|
||||
#if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
|
||||
void HandleSntpResponse(uint64_t aTime, otError aResult);
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
static void HandleLinkMetricsReport(const otIp6Address * aAddress,
|
||||
const otLinkMetricsValues *aMetricsValues,
|
||||
void * aContext);
|
||||
|
||||
void HandleLinkMetricsReport(const otIp6Address *aAddress, const otLinkMetricsValues *aMetricsValues);
|
||||
#endif
|
||||
|
||||
static Interpreter &GetOwner(OwnerLocator &aOwnerLocator);
|
||||
|
||||
static void HandleDiscoveryRequest(const otThreadDiscoveryRequestInfo *aInfo, void *aContext)
|
||||
@@ -626,6 +638,9 @@ private:
|
||||
#if OPENTHREAD_FTD
|
||||
{"leaderpartitionid", &Interpreter::ProcessLeaderPartitionId},
|
||||
{"leaderweight", &Interpreter::ProcessLeaderWeight},
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
{"linkmetrics", &Interpreter::ProcessLinkMetrics},
|
||||
#endif
|
||||
{"log", &Interpreter::ProcessLog},
|
||||
{"mac", &Interpreter::ProcessMac},
|
||||
|
||||
@@ -160,6 +160,10 @@ if (openthread_enable_core_config_args) {
|
||||
defines += [ "OPENTHREAD_CONFIG_LEGACY_ENABLE=1" ]
|
||||
}
|
||||
|
||||
if (openthread_config_link_metrics_enable) {
|
||||
defines += [ "OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE=1" ]
|
||||
}
|
||||
|
||||
if (openthread_config_link_raw_enable) {
|
||||
defines += [ "OPENTHREAD_CONFIG_LINK_RAW_ENABLE=1" ]
|
||||
}
|
||||
@@ -305,6 +309,7 @@ openthread_core_files = [
|
||||
"api/jam_detection_api.cpp",
|
||||
"api/joiner_api.cpp",
|
||||
"api/link_api.cpp",
|
||||
"api/link_metrics_api.cpp",
|
||||
"api/link_raw_api.cpp",
|
||||
"api/logging_api.cpp",
|
||||
"api/message_api.cpp",
|
||||
@@ -500,6 +505,9 @@ openthread_core_files = [
|
||||
"thread/indirect_sender_frame_context.hpp",
|
||||
"thread/key_manager.cpp",
|
||||
"thread/key_manager.hpp",
|
||||
"thread/link_metrics.cpp",
|
||||
"thread/link_metrics.hpp",
|
||||
"thread/link_metrics_tlvs.hpp",
|
||||
"thread/link_quality.cpp",
|
||||
"thread/link_quality.hpp",
|
||||
"thread/lowpan.cpp",
|
||||
|
||||
@@ -88,6 +88,7 @@ set(COMMON_SOURCES
|
||||
api/jam_detection_api.cpp
|
||||
api/joiner_api.cpp
|
||||
api/link_api.cpp
|
||||
api/link_metrics_api.cpp
|
||||
api/link_raw_api.cpp
|
||||
api/logging_api.cpp
|
||||
api/message_api.cpp
|
||||
@@ -184,6 +185,7 @@ set(COMMON_SOURCES
|
||||
thread/energy_scan_server.cpp
|
||||
thread/indirect_sender.cpp
|
||||
thread/key_manager.cpp
|
||||
thread/link_metrics.cpp
|
||||
thread/link_quality.cpp
|
||||
thread/lowpan.cpp
|
||||
thread/mesh_forwarder.cpp
|
||||
|
||||
@@ -130,6 +130,7 @@ SOURCES_COMMON = \
|
||||
api/jam_detection_api.cpp \
|
||||
api/joiner_api.cpp \
|
||||
api/link_api.cpp \
|
||||
api/link_metrics_api.cpp \
|
||||
api/link_raw_api.cpp \
|
||||
api/logging_api.cpp \
|
||||
api/message_api.cpp \
|
||||
@@ -226,6 +227,7 @@ SOURCES_COMMON = \
|
||||
thread/energy_scan_server.cpp \
|
||||
thread/indirect_sender.cpp \
|
||||
thread/key_manager.cpp \
|
||||
thread/link_metrics.cpp \
|
||||
thread/link_quality.cpp \
|
||||
thread/lowpan.cpp \
|
||||
thread/mesh_forwarder.cpp \
|
||||
@@ -451,6 +453,8 @@ HEADERS_COMMON = \
|
||||
thread/indirect_sender.hpp \
|
||||
thread/indirect_sender_frame_context.hpp \
|
||||
thread/key_manager.hpp \
|
||||
thread/link_metrics.hpp \
|
||||
thread/link_metrics_tlvs.hpp \
|
||||
thread/link_quality.hpp \
|
||||
thread/lowpan.hpp \
|
||||
thread/mesh_forwarder.hpp \
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2020, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file implements the OpenThread Link Metrics API.
|
||||
*/
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#include <openthread/link_metrics.h>
|
||||
|
||||
#include "common/instance.hpp"
|
||||
#include "net/ip6_address.hpp"
|
||||
|
||||
using namespace ot;
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
|
||||
otError otLinkMetricsQuery(otInstance * aInstance,
|
||||
const otIp6Address * aDestination,
|
||||
uint8_t aSeriesId,
|
||||
const otLinkMetrics * aLinkMetricsFlags,
|
||||
otLinkMetricsReportCallback aCallback,
|
||||
void * aCallbackContext)
|
||||
{
|
||||
OT_ASSERT(aDestination != nullptr && aLinkMetricsFlags != nullptr);
|
||||
|
||||
static_cast<Instance *>(aInstance)->Get<LinkMetrics>().SetLinkMetricsReportCallback(aCallback, aCallbackContext);
|
||||
|
||||
return static_cast<Instance *>(aInstance)->Get<LinkMetrics>().LinkMetricsQuery(
|
||||
static_cast<const Ip6::Address &>(*aDestination), aSeriesId, *aLinkMetricsFlags);
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
@@ -86,6 +86,10 @@
|
||||
#include "backbone_router/bbr_local.hpp"
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
#include "thread/link_metrics.hpp"
|
||||
#endif
|
||||
|
||||
#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
|
||||
|
||||
#endif // OPENTHREAD_FTD || OPENTHREAD_MTD
|
||||
@@ -789,6 +793,13 @@ template <> inline DuaManager &Instance::Get(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
template <> inline LinkMetrics &Instance::Get(void)
|
||||
{
|
||||
return mThreadNetif.mLinkMetrics;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
|
||||
|
||||
#if OPENTHREAD_CONFIG_OTNS_ENABLE
|
||||
|
||||
@@ -620,6 +620,9 @@ void Message::SetLinkInfo(const ThreadLinkInfo &aLinkInfo)
|
||||
SetLinkSecurityEnabled(aLinkInfo.mLinkSecurity);
|
||||
SetPanId(aLinkInfo.mPanId);
|
||||
AddRss(aLinkInfo.mRss);
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
AddLqi(aLinkInfo.mLqi);
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
||||
SetTimeSyncSeq(aLinkInfo.mTimeSyncSeq);
|
||||
SetNetworkTimeOffset(aLinkInfo.mNetworkTimeOffset);
|
||||
|
||||
@@ -151,6 +151,9 @@ struct MessageMetadata
|
||||
uint16_t mLength; ///< Number of bytes within the message.
|
||||
uint16_t mOffset; ///< A byte offset within the message.
|
||||
RssAverager mRssAverager; ///< The averager maintaining the received signal strength (RSS) average.
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
LqiAverager mLqiAverager; ///< The averager maintaining the Link quality indicator (LQI) average.
|
||||
#endif
|
||||
|
||||
ChildMask mChildMask; ///< A ChildMask to indicate which sleepy children need to receive this.
|
||||
uint16_t mMeshDest; ///< Used for unicast non-link-local messages.
|
||||
@@ -883,6 +886,35 @@ public:
|
||||
*/
|
||||
const RssAverager &GetRssAverager(void) const { return GetMetadata().mRssAverager; }
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
/**
|
||||
* This method updates the average LQI (Link Quality Indicator) associated with the message.
|
||||
*
|
||||
* The given LQI value would be added to the average. Note that a message can be composed of multiple 802.15.4
|
||||
* frame fragments each received with a different signal strength.
|
||||
*
|
||||
* @param[in] aLQI A new LQI value (has no unit) to be added to average.
|
||||
*
|
||||
*/
|
||||
void AddLqi(uint8_t aLqi) { GetMetadata().mLqiAverager.Add(aLqi); }
|
||||
|
||||
/**
|
||||
* This method returns the average LQI (Link Quality Indicator) associated with the message.
|
||||
*
|
||||
* @returns The current average LQI value (in dBm) or OT_RADIO_LQI_NONE if no average is available.
|
||||
*
|
||||
*/
|
||||
uint8_t GetAverageLqi(void) const { return GetMetadata().mLqiAverager.GetAverage(); }
|
||||
|
||||
/**
|
||||
* This mehod returns the count of frames counted so far.
|
||||
*
|
||||
* @retruns The count of frames that have been counted.
|
||||
*
|
||||
*/
|
||||
uint8_t GetPsduCount(void) const { return GetMetadata().mLqiAverager.GetCount(); }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This method sets the message's link info properties (PAN ID, link security, RSS) from a given `ThreadLinkInfo`.
|
||||
*
|
||||
|
||||
@@ -240,4 +240,14 @@
|
||||
#define OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
*
|
||||
* Define as 1 to enable Link Metrics feature.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE 0
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_MLE_H_
|
||||
|
||||
@@ -523,6 +523,12 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
|
||||
#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_DUA_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
|
||||
#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_DUA_ENABLE"
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* Copyright (c) 2020, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file includes definitions for Thread Link Metrics.
|
||||
*/
|
||||
|
||||
#include "link_metrics.hpp"
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "common/instance.hpp"
|
||||
#include "common/locator-getters.hpp"
|
||||
#include "common/logging.hpp"
|
||||
|
||||
#include "link_metrics_tlvs.hpp"
|
||||
|
||||
namespace ot {
|
||||
|
||||
LinkMetrics::LinkMetrics(Instance &aInstance)
|
||||
: InstanceLocator(aInstance)
|
||||
, mLinkMetricsReportCallback(nullptr)
|
||||
, mLinkMetricsReportCallbackContext(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
otError LinkMetrics::LinkMetricsQuery(const Ip6::Address & aDestination,
|
||||
uint8_t aSeriesId,
|
||||
const otLinkMetrics &aLinkMetricsFlags)
|
||||
{
|
||||
otError error;
|
||||
LinkMetricsTypeIdFlags typeIdFlags[kMaxTypeIdFlags];
|
||||
uint8_t typeIdFlagsCount = TypeIdFlagsFromLinkMetricsFlags(typeIdFlags, aLinkMetricsFlags);
|
||||
|
||||
error = SendLinkMetricsQuery(aDestination, aSeriesId, typeIdFlags, typeIdFlagsCount);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
otError LinkMetrics::AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
Tlv tlv;
|
||||
uint8_t queryId;
|
||||
bool hasQueryId = false;
|
||||
uint8_t length = 0;
|
||||
uint16_t startOffset = aMessage.GetLength();
|
||||
uint16_t offset;
|
||||
uint16_t endOffset;
|
||||
otLinkMetrics linkMetrics;
|
||||
|
||||
memset(&linkMetrics, 0, sizeof(linkMetrics));
|
||||
|
||||
SuccessOrExit(error = Tlv::FindTlvValueOffset(aRequestMessage, Mle::Tlv::Type::kLinkMetricsQuery, offset,
|
||||
endOffset)); // `endOffset` is used to store tlv length here
|
||||
|
||||
endOffset = offset + endOffset;
|
||||
|
||||
while (offset < endOffset)
|
||||
{
|
||||
VerifyOrExit(aRequestMessage.Read(offset, sizeof(tlv), &tlv) == sizeof(tlv), error = OT_ERROR_PARSE);
|
||||
|
||||
switch (tlv.GetType())
|
||||
{
|
||||
case kLinkMetricsQueryId:
|
||||
SuccessOrExit(error = Tlv::ReadUint8Tlv(aRequestMessage, offset, queryId));
|
||||
hasQueryId = true;
|
||||
break;
|
||||
|
||||
case kLinkMetricsQueryOptions:
|
||||
for (uint16_t index = offset + sizeof(tlv), endIndex = static_cast<uint16_t>(offset + tlv.GetSize());
|
||||
index < endIndex; index += sizeof(LinkMetricsTypeIdFlags))
|
||||
{
|
||||
LinkMetricsTypeIdFlags typeIdFlags;
|
||||
|
||||
VerifyOrExit(aRequestMessage.Read(index, sizeof(typeIdFlags), &typeIdFlags) == sizeof(typeIdFlags),
|
||||
error = OT_ERROR_PARSE);
|
||||
|
||||
switch (typeIdFlags.GetRawValue())
|
||||
{
|
||||
case kTypeIdFlagPdu:
|
||||
VerifyOrExit(!linkMetrics.mPduCount, error = OT_ERROR_PARSE);
|
||||
linkMetrics.mPduCount = true;
|
||||
break;
|
||||
|
||||
case kTypeIdFlagLqi:
|
||||
VerifyOrExit(!linkMetrics.mLqi, error = OT_ERROR_PARSE);
|
||||
linkMetrics.mLqi = true;
|
||||
break;
|
||||
|
||||
case kTypeIdFlagLinkMargin:
|
||||
VerifyOrExit(!linkMetrics.mLinkMargin, error = OT_ERROR_PARSE);
|
||||
linkMetrics.mLinkMargin = true;
|
||||
break;
|
||||
|
||||
case kTypeIdFlagRssi:
|
||||
VerifyOrExit(!linkMetrics.mRssi, error = OT_ERROR_PARSE);
|
||||
linkMetrics.mRssi = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (typeIdFlags.IsExtendedFlagSet())
|
||||
{
|
||||
index += sizeof(uint8_t); // Skip the additional second flags byte.
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
offset += tlv.GetSize();
|
||||
}
|
||||
|
||||
VerifyOrExit(hasQueryId, error = OT_ERROR_PARSE);
|
||||
|
||||
// Link Metrics Report TLV
|
||||
tlv.SetType(Mle::Tlv::kLinkMetricsReport);
|
||||
SuccessOrExit(error = aMessage.Append(&tlv, sizeof(tlv)));
|
||||
|
||||
if (queryId == 0)
|
||||
{
|
||||
SuccessOrExit(error = AppendSingleProbeLinkMetricsReport(aMessage, length, linkMetrics, aRequestMessage));
|
||||
}
|
||||
else
|
||||
{
|
||||
ExitNow(error = OT_ERROR_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
tlv.SetLength(length);
|
||||
aMessage.Write(startOffset, sizeof(tlv), &tlv);
|
||||
|
||||
exit:
|
||||
otLogDebgMle("AppendLinkMetricsReport, error:%s", otThreadErrorToString(error));
|
||||
return error;
|
||||
}
|
||||
|
||||
void LinkMetrics::HandleLinkMetricsReport(const Message & aMessage,
|
||||
uint16_t aOffset,
|
||||
uint16_t aLength,
|
||||
const Ip6::Address &aAddress)
|
||||
{
|
||||
otLinkMetricsValues metricsValues;
|
||||
uint8_t metricsRawValue;
|
||||
uint16_t pos = aOffset;
|
||||
uint16_t endPos = aOffset + aLength;
|
||||
Tlv tlv;
|
||||
LinkMetricsTypeIdFlags typeIdFlags;
|
||||
|
||||
VerifyOrExit(mLinkMetricsReportCallback != nullptr, OT_NOOP);
|
||||
|
||||
memset(&metricsValues, 0, sizeof(metricsValues));
|
||||
|
||||
otLogDebgMle("Received Link Metrics Report");
|
||||
|
||||
while (pos < endPos)
|
||||
{
|
||||
VerifyOrExit(aMessage.Read(pos, sizeof(Tlv), &tlv) == sizeof(Tlv), OT_NOOP);
|
||||
VerifyOrExit(tlv.GetType() == kLinkMetricsReportSub, OT_NOOP);
|
||||
pos += sizeof(Tlv);
|
||||
VerifyOrExit(pos + tlv.GetLength() <= endPos, OT_NOOP);
|
||||
|
||||
aMessage.Read(pos, sizeof(LinkMetricsTypeIdFlags), &typeIdFlags);
|
||||
if (typeIdFlags.IsExtendedFlagSet())
|
||||
{
|
||||
pos += tlv.GetLength(); // Skip the whole sub-TLV if `E` flag is set
|
||||
continue;
|
||||
}
|
||||
pos += sizeof(LinkMetricsTypeIdFlags);
|
||||
|
||||
switch (typeIdFlags.GetRawValue())
|
||||
{
|
||||
case kTypeIdFlagPdu:
|
||||
metricsValues.mMetrics.mPduCount = true;
|
||||
aMessage.Read(pos, sizeof(uint32_t), &metricsValues.mPduCountValue);
|
||||
pos += sizeof(uint32_t);
|
||||
otLogDebgMle(" - PDU Counter: %d (Count/Summation)", metricsValues.mPduCountValue);
|
||||
break;
|
||||
|
||||
case kTypeIdFlagLqi:
|
||||
metricsValues.mMetrics.mLqi = true;
|
||||
aMessage.Read(pos, sizeof(uint8_t), &metricsValues.mLqiValue);
|
||||
pos += sizeof(uint8_t);
|
||||
otLogDebgMle(" - LQI: %d (Exponential Moving Average)", metricsValues.mLqiValue);
|
||||
break;
|
||||
|
||||
case kTypeIdFlagLinkMargin:
|
||||
metricsValues.mMetrics.mLinkMargin = true;
|
||||
aMessage.Read(pos, sizeof(uint8_t), &metricsRawValue);
|
||||
metricsValues.mLinkMarginValue =
|
||||
metricsRawValue * 130 / 255; // Reverse operation for linear scale, map from [0, 255] to [0, 130]
|
||||
pos += sizeof(uint8_t);
|
||||
otLogDebgMle(" - Margin: %d (dB) (Exponential Moving Average)", metricsValues.mLinkMarginValue);
|
||||
break;
|
||||
|
||||
case kTypeIdFlagRssi:
|
||||
metricsValues.mMetrics.mRssi = true;
|
||||
aMessage.Read(pos, sizeof(uint8_t), &metricsRawValue);
|
||||
metricsValues.mRssiValue =
|
||||
metricsRawValue * 130 / 255 - 130; // Reverse operation for linear scale, map from [0, 255] to [-130, 0]
|
||||
pos += sizeof(uint8_t);
|
||||
otLogDebgMle(" - RSSI: %d (dBm) (Exponential Moving Average)", metricsValues.mRssiValue);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mLinkMetricsReportCallback(&aAddress, &metricsValues, mLinkMetricsReportCallbackContext);
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void LinkMetrics::SetLinkMetricsReportCallback(otLinkMetricsReportCallback aCallback, void *aCallbackContext)
|
||||
{
|
||||
mLinkMetricsReportCallback = aCallback;
|
||||
mLinkMetricsReportCallbackContext = aCallbackContext;
|
||||
}
|
||||
|
||||
otError LinkMetrics::SendLinkMetricsQuery(const Ip6::Address & aDestination,
|
||||
uint8_t aSeriesId,
|
||||
const LinkMetricsTypeIdFlags *aTypeIdFlags,
|
||||
uint8_t aTypeIdFlagsCount)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
LinkMetricsQueryOptionsTlv linkMetricsQueryOptionsTlv;
|
||||
uint8_t length = 0;
|
||||
static const uint8_t tlvs[] = {Mle::Tlv::kLinkMetricsReport};
|
||||
uint8_t buf[sizeof(Tlv) * 3 + sizeof(uint8_t) +
|
||||
sizeof(LinkMetricsTypeIdFlags) *
|
||||
kMaxTypeIdFlags]; // LinkMetricsQuery Tlv + LinkMetricsQueryId sub-TLV (value-length: 1 byte) +
|
||||
// LinkMetricsQueryOptions sub-TLV (value-length: `kMaxTypeIdFlags` bytes)
|
||||
Tlv *tlv = reinterpret_cast<Tlv *>(buf);
|
||||
Tlv subTlv;
|
||||
|
||||
// Link Metrics Query TLV
|
||||
tlv->SetType(Mle::Tlv::kLinkMetricsQuery);
|
||||
length += sizeof(Tlv);
|
||||
|
||||
// Link Metrics Query ID sub-TLV
|
||||
subTlv.SetType(kLinkMetricsQueryId);
|
||||
subTlv.SetLength(sizeof(uint8_t));
|
||||
memcpy(buf + length, &subTlv, sizeof(subTlv));
|
||||
length += sizeof(subTlv);
|
||||
memcpy(buf + length, &aSeriesId, sizeof(aSeriesId));
|
||||
length += sizeof(aSeriesId);
|
||||
|
||||
// Link Metrics Query Options sub-TLV
|
||||
if (aTypeIdFlagsCount > 0)
|
||||
{
|
||||
linkMetricsQueryOptionsTlv.Init();
|
||||
linkMetricsQueryOptionsTlv.SetLength(aTypeIdFlagsCount * sizeof(LinkMetricsTypeIdFlags));
|
||||
|
||||
memcpy(buf + length, &linkMetricsQueryOptionsTlv, sizeof(linkMetricsQueryOptionsTlv));
|
||||
length += sizeof(linkMetricsQueryOptionsTlv);
|
||||
memcpy(buf + length, aTypeIdFlags, linkMetricsQueryOptionsTlv.GetLength());
|
||||
length += linkMetricsQueryOptionsTlv.GetLength();
|
||||
}
|
||||
|
||||
// Set Length for Link Metrics Report TLV
|
||||
tlv->SetLength(length - sizeof(Tlv));
|
||||
|
||||
SuccessOrExit(error = Get<Mle::MleRouter>().SendDataRequest(aDestination, tlvs, sizeof(tlvs), 0, buf, length));
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError LinkMetrics::AppendSingleProbeLinkMetricsReport(Message & aMessage,
|
||||
uint8_t & aLength,
|
||||
const otLinkMetrics &aLinkMetrics,
|
||||
const Message & aRequestMessage)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
LinkMetricsReportSubTlv metric;
|
||||
|
||||
aLength = 0;
|
||||
|
||||
// Link Metrics Report sub-TLVs
|
||||
if (aLinkMetrics.mPduCount)
|
||||
{
|
||||
metric.Init();
|
||||
metric.SetMetricsTypeId(kTypeIdFlagPdu);
|
||||
metric.SetMetricsValue32(aRequestMessage.GetPsduCount());
|
||||
SuccessOrExit(error = aMessage.Append(&metric, metric.GetSize()));
|
||||
aLength += metric.GetSize();
|
||||
}
|
||||
|
||||
if (aLinkMetrics.mLqi)
|
||||
{
|
||||
metric.Init();
|
||||
metric.SetMetricsTypeId(kTypeIdFlagLqi);
|
||||
metric.SetMetricsValue8(aRequestMessage.GetAverageLqi()); // IEEE 802.15.4 LQI is in scale 0-255
|
||||
SuccessOrExit(error = aMessage.Append(&metric, metric.GetSize()));
|
||||
aLength += metric.GetSize();
|
||||
}
|
||||
|
||||
if (aLinkMetrics.mLinkMargin)
|
||||
{
|
||||
metric.Init();
|
||||
metric.SetMetricsTypeId(kTypeIdFlagLinkMargin);
|
||||
metric.SetMetricsValue8(
|
||||
LinkQualityInfo::ConvertRssToLinkMargin(Get<Mac::Mac>().GetNoiseFloor(), aRequestMessage.GetAverageRss()) *
|
||||
255 / 130); // Linear scale Link Margin from [0, 130] to [0, 255]
|
||||
SuccessOrExit(error = aMessage.Append(&metric, metric.GetSize()));
|
||||
aLength += metric.GetSize();
|
||||
}
|
||||
|
||||
if (aLinkMetrics.mRssi)
|
||||
{
|
||||
metric.Init();
|
||||
metric.SetMetricsTypeId(kTypeIdFlagRssi);
|
||||
metric.SetMetricsValue8((aRequestMessage.GetAverageRss() + 130) * 255 /
|
||||
130); // Linear scale rss from [-130, 0] to [0, 255]
|
||||
SuccessOrExit(error = aMessage.Append(&metric, metric.GetSize()));
|
||||
aLength += metric.GetSize();
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
uint8_t LinkMetrics::TypeIdFlagsFromLinkMetricsFlags(LinkMetricsTypeIdFlags *aTypeIdFlags,
|
||||
const otLinkMetrics & aLinkMetricsFlags)
|
||||
{
|
||||
uint8_t count = 0;
|
||||
|
||||
if (aLinkMetricsFlags.mPduCount)
|
||||
{
|
||||
aTypeIdFlags[count++].SetRawValue(kTypeIdFlagPdu);
|
||||
}
|
||||
|
||||
if (aLinkMetricsFlags.mLqi)
|
||||
{
|
||||
aTypeIdFlags[count++].SetRawValue(kTypeIdFlagLqi);
|
||||
}
|
||||
|
||||
if (aLinkMetricsFlags.mLinkMargin)
|
||||
{
|
||||
aTypeIdFlags[count++].SetRawValue(kTypeIdFlagLinkMargin);
|
||||
}
|
||||
|
||||
if (aLinkMetricsFlags.mRssi)
|
||||
{
|
||||
aTypeIdFlags[count++].SetRawValue(kTypeIdFlagRssi);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright (c) 2020, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file includes definitions for Thread Link Metrics query and management.
|
||||
*/
|
||||
|
||||
#ifndef LINK_METRICS_HPP_
|
||||
#define LINK_METRICS_HPP_
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
|
||||
#include <openthread/ip6.h>
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "common/locator.hpp"
|
||||
|
||||
#include "link_metrics_tlvs.hpp"
|
||||
#include "topology.hpp"
|
||||
|
||||
namespace ot {
|
||||
|
||||
/**
|
||||
* @addtogroup core-link-metrics
|
||||
*
|
||||
* @brief
|
||||
* This module includes definitions for Thread Link Metrics query and management.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
class LinkMetrics : public InstanceLocator
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* This constructor initializes an instance of the LinkMetrics class.
|
||||
*
|
||||
* @param[in] aInstance A reference to the OpenThread interface.
|
||||
*
|
||||
*/
|
||||
explicit LinkMetrics(Instance &aInstance);
|
||||
|
||||
/**
|
||||
* This function sends an MLE Data Request containing Link Metrics Query TLV to query Link Metrics data.
|
||||
*
|
||||
* It could be either a Single Probe or a Forward Tracking Series.
|
||||
*
|
||||
* @param[in] aDestination A reference to the IPv6 address of the destination.
|
||||
* @param[in] aSeriesId The Series ID to query, 0 for single probe.
|
||||
* @param[in] aLinkMetricsFlags A reference to flags specifying what metrics to query.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully sent a Link Metrics query message.
|
||||
* @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Data Request message.
|
||||
* @retval OT_ERROR_INVALID_ARGS TypeIdFlags are not valid or exceed the count limit.
|
||||
*
|
||||
*/
|
||||
otError LinkMetricsQuery(const Ip6::Address & aDestination,
|
||||
uint8_t aSeriesId,
|
||||
const otLinkMetrics &aLinkMetricsFlags);
|
||||
|
||||
/**
|
||||
* This method appends a Link Metrics Report to a message according to the Link Metrics query.
|
||||
*
|
||||
* @param[out] aMessage A reference to the message to append report.
|
||||
* @param[in] aRequestMessage A reference to the message of the Data Request.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully appended the Thread Discovery TLV.
|
||||
* @retval OT_ERROR_PARSE Cannot parse query sub TLV successfully.
|
||||
* @retval OT_ERROR_INVALID_ARGS QueryId is invalid or any Type ID is invalid.
|
||||
*
|
||||
*/
|
||||
otError AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage);
|
||||
|
||||
/**
|
||||
* This method handles the received Link Metrics report contained in @p aMessage.
|
||||
*
|
||||
* @param[in] aMessage A reference to the message.
|
||||
* @param[in] aOffset The offset in bytes where the metrics report sub-TLVs start.
|
||||
* @param[in] aLength The length of the metrics report sub-TLVs in bytes.
|
||||
* @param[in] aAddress A reference to the source address of the message.
|
||||
*
|
||||
*/
|
||||
void HandleLinkMetricsReport(const Message & aMessage,
|
||||
uint16_t aOffset,
|
||||
uint16_t aLength,
|
||||
const Ip6::Address &aAddress);
|
||||
|
||||
/**
|
||||
* This method registers a callback to handle Link Metrics report received.
|
||||
*
|
||||
* @param[in] aCallback A pointer to a function that is called when link probing report is received
|
||||
* @param[in] aCallbackContext A pointer to application-specific context.
|
||||
*
|
||||
*/
|
||||
void SetLinkMetricsReportCallback(otLinkMetricsReportCallback aCallback, void *aCallbackContext);
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
kMaxTypeIdFlags = 4,
|
||||
|
||||
kTypeIdFlagPdu =
|
||||
0x40, ///< 0x0_1_000_000 -> 0x40 ==> L bit set, type = 0 (count/summation), metric-enum = 0 (PDU rxed)
|
||||
kTypeIdFlagLqi = 0x09, ///< 0x0_0_001_001 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 1 (LQI)
|
||||
kTypeIdFlagLinkMargin =
|
||||
0x0a, ///< 0x0_0_001_010 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 2 (Link Margin)
|
||||
kTypeIdFlagRssi = 0x0b, ///< 0x0_0_001_011 -> 0x00 ==> L bit not set, type = 1 (exp ave), metric-enum = 3 (RSSI)
|
||||
};
|
||||
|
||||
otLinkMetricsReportCallback mLinkMetricsReportCallback;
|
||||
void * mLinkMetricsReportCallbackContext;
|
||||
|
||||
otError SendLinkMetricsQuery(const Ip6::Address & aDestination,
|
||||
uint8_t aSeriesId,
|
||||
const LinkMetricsTypeIdFlags *aTypeIdFlags,
|
||||
uint8_t aTypeIdFlagsCount);
|
||||
|
||||
otError AppendSingleProbeLinkMetricsReport(Message & aMessage,
|
||||
uint8_t & aLength,
|
||||
const otLinkMetrics &aLinkMetrics,
|
||||
const Message & aRequestMessage);
|
||||
|
||||
static uint8_t TypeIdFlagsFromLinkMetricsFlags(LinkMetricsTypeIdFlags *aTypeIdFlags,
|
||||
const otLinkMetrics & aLinkMetricsFlags);
|
||||
};
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
|
||||
#endif // LINK_METRICS_HPP
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright (c) 2020, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file includes definitions for generating and processing Link Metrics TLVs.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LINK_METRICS_TLVS_HPP_
|
||||
#define LINK_METRICS_TLVS_HPP_
|
||||
|
||||
#include <openthread/link_metrics.h>
|
||||
|
||||
#include "common/encoding.hpp"
|
||||
#include "common/message.hpp"
|
||||
#include "common/tlvs.hpp"
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
|
||||
namespace ot {
|
||||
|
||||
/**
|
||||
* Link Metrics parameters
|
||||
*
|
||||
*/
|
||||
enum
|
||||
{
|
||||
kLinkMetricsMaxTypeIdFlags = 4, ///< Max count of Link Metrics Type ID Flags.
|
||||
};
|
||||
|
||||
/**
|
||||
* Link Metrics Sub-TLV types
|
||||
*
|
||||
*/
|
||||
enum Type
|
||||
{
|
||||
kLinkMetricsReportSub = 0, ///< Link Metrics Report Sub-TLV
|
||||
kLinkMetricsQueryId = 1, ///< Link Metrics Query ID Sub-TLV
|
||||
kLinkMetricsQueryOptions = 2, ///< Link Metrics Query Options Sub-TLV
|
||||
kForwardProbingRegistration = 3, ///< Forward Probing Registration Sub-TLV
|
||||
kReverseProbingRegistration = 4, ///< Reverse Probing Registration Sub-TLV
|
||||
kLinkMetricsStatus = 5, ///< Link Metrics Status Sub-TLV
|
||||
kSeriesTrackingCapabilities = 6, ///< Series Tracking Capabilities Sub-TLV
|
||||
kEnhancedACKConfiguration = 7, ///< Enhanced ACK Configuration Sub-TLV
|
||||
};
|
||||
|
||||
/**
|
||||
* This class implements Link Metrics Type Id Flags generation and parsing.
|
||||
*
|
||||
*/
|
||||
OT_TOOL_PACKED_BEGIN
|
||||
class LinkMetricsTypeIdFlags
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
*/
|
||||
LinkMetricsTypeIdFlags(void) {}
|
||||
|
||||
/**
|
||||
* Constructor for implicit cast from `uint8_t` to `LinkMetricsTypeIdFlags`.
|
||||
*
|
||||
*/
|
||||
LinkMetricsTypeIdFlags(uint8_t typeIdFlags)
|
||||
: mTypeIdFlags(typeIdFlags)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* This method init the Type Id value
|
||||
*
|
||||
*/
|
||||
void Init(void) { mTypeIdFlags = 0; }
|
||||
|
||||
/**
|
||||
* This method clears the Extended flag.
|
||||
*
|
||||
*/
|
||||
void ClearExtendedFlag(void) { mTypeIdFlags &= ~kExtendedFlag; }
|
||||
|
||||
/**
|
||||
* This method sets the Extended flag, indicating an additional second flags byte after the current 1-byte flags.
|
||||
* MUST NOT set in Thread 1.2.1.
|
||||
*
|
||||
*/
|
||||
void SetExtendedFlag(void) { mTypeIdFlags |= kExtendedFlag; }
|
||||
|
||||
/**
|
||||
* This method indicates whether or not the Extended flag is set.
|
||||
*
|
||||
* @retval true The Extended flag is set.
|
||||
* @retval false The Extended flag is not set.
|
||||
*
|
||||
*/
|
||||
bool IsExtendedFlagSet(void) const { return (mTypeIdFlags & kExtendedFlag) != 0; }
|
||||
|
||||
/**
|
||||
* This method clears value length flag.
|
||||
*
|
||||
*/
|
||||
void ClearLengthFlag(void) { mTypeIdFlags &= ~kLengthFlag; }
|
||||
|
||||
/**
|
||||
* This method sets the value length flag.
|
||||
*
|
||||
*/
|
||||
void SetLengthFlag(void) { mTypeIdFlags |= kLengthFlag; }
|
||||
|
||||
/**
|
||||
* This method indicates whether or not the value length flag is set.
|
||||
*
|
||||
* @retval true The value length flag is set, extended value length (4 bytes)
|
||||
* @retval false The value length flag is not set, short value length (1 byte)
|
||||
*
|
||||
*/
|
||||
bool IsLengthFlagSet(void) const { return (mTypeIdFlags & kLengthFlag) != 0; }
|
||||
|
||||
/**
|
||||
* This method sets the Type/Average Enum.
|
||||
*
|
||||
* @param[in] aTypeEnum Type/Average Enum.
|
||||
*
|
||||
*/
|
||||
void SetTypeEnum(uint8_t aTypeEnum)
|
||||
{
|
||||
mTypeIdFlags = (mTypeIdFlags & ~kTypeEnumMask) | ((aTypeEnum << kTypeEnumOffset) & kTypeEnumMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the Type/Average Enum.
|
||||
*
|
||||
* @returns The Type/Average Enum.
|
||||
*
|
||||
*/
|
||||
uint8_t GetTypeEnum(void) const { return (mTypeIdFlags & kTypeEnumMask) >> kTypeEnumOffset; }
|
||||
|
||||
/**
|
||||
* This method sets the Metric Enum.
|
||||
*
|
||||
* @param[in] aMetricEnum Metric Enum.
|
||||
*
|
||||
*/
|
||||
void SetMetricEnum(uint8_t aMetricEnum)
|
||||
{
|
||||
mTypeIdFlags = (mTypeIdFlags & ~kMetricEnumMask) | ((aMetricEnum << kMetricEnumOffset) & kMetricEnumMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the Metric Enum.
|
||||
*
|
||||
* @returns The Metric Enum.
|
||||
*
|
||||
*/
|
||||
uint8_t GetMetricEnum(void) const { return (mTypeIdFlags & kMetricEnumMask) >> kMetricEnumOffset; }
|
||||
|
||||
/**
|
||||
* This method returns the raw value of the entire TypeIdFlags.
|
||||
*
|
||||
* @returns The raw value of TypeIdFlags.
|
||||
*
|
||||
*/
|
||||
uint8_t GetRawValue(void) const { return mTypeIdFlags; }
|
||||
|
||||
/**
|
||||
* This method sets the raw value of the entire TypeIdFlags.
|
||||
*
|
||||
* @param[in] aTypeIdFlags The value of entire TypeIdFlags.
|
||||
*
|
||||
*/
|
||||
void SetRawValue(uint8_t aTypeIdFlags) { mTypeIdFlags = aTypeIdFlags; }
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
kLengthFlag = 1 << 6,
|
||||
kExtendedFlag = 1 << 7,
|
||||
kTypeEnumOffset = 3,
|
||||
kTypeEnumMask = 7 << kTypeEnumOffset,
|
||||
kMetricEnumOffset = 0,
|
||||
kMetricEnumMask = 7 << kMetricEnumOffset,
|
||||
};
|
||||
|
||||
uint8_t mTypeIdFlags;
|
||||
} OT_TOOL_PACKED_END;
|
||||
|
||||
/**
|
||||
* This class implements Link Metrics Report Sub-TLV generation and parsing.
|
||||
*
|
||||
*/
|
||||
OT_TOOL_PACKED_BEGIN
|
||||
class LinkMetricsReportSubTlv : public Tlv
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* This method initializes the TLV.
|
||||
*
|
||||
*/
|
||||
void Init(void)
|
||||
{
|
||||
SetType(kLinkMetricsReportSub);
|
||||
SetLength(sizeof(*this) - sizeof(Tlv));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicates whether or not the TLV appears to be well-formed.
|
||||
*
|
||||
* @retval true The TLV appears to be well-formed.
|
||||
* @retval false The TLV does not appear to be well-formed.
|
||||
*
|
||||
*/
|
||||
bool IsValid(void) const { return GetLength() >= sizeof(LinkMetricsTypeIdFlags) + sizeof(uint8_t); }
|
||||
|
||||
/**
|
||||
* This method returns the Link Metrics Type ID.
|
||||
*
|
||||
* @returns The Link Metrics Type ID.
|
||||
*
|
||||
*/
|
||||
LinkMetricsTypeIdFlags GetMetricsTypeId(void) const { return mMetricsTypeId; }
|
||||
|
||||
/**
|
||||
* This method sets the Link Metrics Type ID.
|
||||
*
|
||||
* @param[in] aMetricsTypeID The Link Metrics Type ID to set.
|
||||
*
|
||||
*/
|
||||
void SetMetricsTypeId(LinkMetricsTypeIdFlags aMetricsTypeId)
|
||||
{
|
||||
mMetricsTypeId = aMetricsTypeId;
|
||||
if (!aMetricsTypeId.IsLengthFlagSet())
|
||||
{
|
||||
SetLength(sizeof(*this) - sizeof(Tlv) - sizeof(uint32_t) + sizeof(uint8_t)); // The value is 1 byte long
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the metric value in 8 bits.
|
||||
*
|
||||
* @returns The metric value.
|
||||
*
|
||||
*/
|
||||
uint8_t GetMetricsValue8(void) const { return mMetricsValue.m8; }
|
||||
|
||||
/**
|
||||
* This method returns the metric value in 32 bits.
|
||||
*
|
||||
* @returns The metric value.
|
||||
*
|
||||
*/
|
||||
uint32_t GetMetricsValue32(void) const { return mMetricsValue.m32; }
|
||||
|
||||
/**
|
||||
* This method sets the metric value (8 bits).
|
||||
*
|
||||
* @param[in] aMetricsValue Metrics value.
|
||||
*
|
||||
*/
|
||||
void SetMetricsValue8(uint8_t aMetricsValue) { mMetricsValue.m8 = aMetricsValue; }
|
||||
|
||||
/**
|
||||
* This method sets the metric value (32 bits).
|
||||
*
|
||||
* @param[in] aMetricsValue Metrics value.
|
||||
*
|
||||
*/
|
||||
void SetMetricsValue32(uint32_t aMetricsValue) { mMetricsValue.m32 = aMetricsValue; }
|
||||
|
||||
private:
|
||||
LinkMetricsTypeIdFlags mMetricsTypeId;
|
||||
union
|
||||
{
|
||||
uint8_t m8;
|
||||
uint32_t m32;
|
||||
} mMetricsValue;
|
||||
} OT_TOOL_PACKED_END;
|
||||
|
||||
/**
|
||||
* This class implements Link Metrics Query Options Sub-TLV generation and parsing.
|
||||
*
|
||||
*/
|
||||
OT_TOOL_PACKED_BEGIN
|
||||
class LinkMetricsQueryOptionsTlv : public Tlv
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* This method initializes the TLV.
|
||||
*
|
||||
*/
|
||||
void Init(void)
|
||||
{
|
||||
SetType(kLinkMetricsQueryOptions);
|
||||
SetLength(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method indicates whether or not the TLV appears to be well-formed.
|
||||
*
|
||||
* @retval TRUE If the TLV appears to be well-formed.
|
||||
* @retval FALSE If the TLV does not appear to be well-formed.
|
||||
*
|
||||
*/
|
||||
bool IsValid(void) const { return GetLength() >= sizeof(LinkMetricsTypeIdFlags); }
|
||||
|
||||
} OT_TOOL_PACKED_END;
|
||||
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
|
||||
#endif // LINK_METRICS_TLVS_HPP_
|
||||
@@ -121,6 +121,25 @@ exit:
|
||||
return string;
|
||||
}
|
||||
|
||||
void LqiAverager::Reset(void)
|
||||
{
|
||||
mCount = 0;
|
||||
mAverage = 0;
|
||||
}
|
||||
|
||||
void LqiAverager::Add(uint8_t aLqi)
|
||||
{
|
||||
uint8_t count;
|
||||
|
||||
if (mCount < UINT8_MAX)
|
||||
{
|
||||
mCount++;
|
||||
}
|
||||
count = OT_MIN((1 << kCoeffBitShift), mCount);
|
||||
|
||||
mAverage = static_cast<uint8_t>(((mAverage * (count - 1)) + aLqi) / count);
|
||||
}
|
||||
|
||||
void LinkQualityInfo::Clear(void)
|
||||
{
|
||||
mRssAverager.Reset();
|
||||
|
||||
@@ -211,6 +211,55 @@ private:
|
||||
uint16_t mCount : 5; // Number of RSS values added to averager so far (limited to 2^kCoeffBitShift-1).
|
||||
};
|
||||
|
||||
/**
|
||||
* This class implements a Link Quality Indicator (LQI) averager.
|
||||
*
|
||||
* It maintains the exponential moving average value of LQI.
|
||||
*
|
||||
*/
|
||||
class LqiAverager
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* This method resets the averager and clears the average value.
|
||||
*
|
||||
*/
|
||||
void Reset(void);
|
||||
|
||||
/**
|
||||
* This method adds a link quality indicator (LQI) value to the average.
|
||||
*
|
||||
* @param[in] aLqi Link Quality Indicator value to be added to the average.
|
||||
*
|
||||
*/
|
||||
void Add(uint8_t aLqi);
|
||||
|
||||
/**
|
||||
* This method returns the current average link quality value maintained by the averager.
|
||||
*
|
||||
* @returns The current average value.
|
||||
*
|
||||
*/
|
||||
uint8_t GetAverage(void) const { return mAverage; }
|
||||
|
||||
/**
|
||||
* This method returns the count of frames calculated so far.
|
||||
*
|
||||
* @returns The count of frames calculated.
|
||||
*
|
||||
*/
|
||||
uint8_t GetCount(void) const { return mCount; }
|
||||
|
||||
private:
|
||||
enum
|
||||
{
|
||||
kCoeffBitShift = 3, ///< Coefficient used for exponentially weighted filter (1 << kCoeffBitShift).
|
||||
};
|
||||
|
||||
uint8_t mAverage; ///< The average link quality indicator value.
|
||||
uint8_t mCount; ///< Number of LQI values added to averager so far.
|
||||
};
|
||||
|
||||
/**
|
||||
* This class encapsulates/stores all relevant information about quality of a link, including average received signal
|
||||
* strength (RSS), last RSS, link margin, and link quality.
|
||||
|
||||
@@ -1042,6 +1042,9 @@ void MeshForwarder::HandleFragment(const uint8_t * aFrame,
|
||||
message->Write(message->GetOffset(), aFrameLength, aFrame);
|
||||
message->MoveOffset(aFrameLength);
|
||||
message->AddRss(aLinkInfo.GetRss());
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
message->AddLqi(aLinkInfo.GetLqi());
|
||||
#endif
|
||||
message->SetTimeout(kReassemblyTimeout);
|
||||
}
|
||||
|
||||
|
||||
+23
-1
@@ -2035,8 +2035,13 @@ exit:
|
||||
otError Mle::SendDataRequest(const Ip6::Address &aDestination,
|
||||
const uint8_t * aTlvs,
|
||||
uint8_t aTlvsLength,
|
||||
uint16_t aDelay)
|
||||
uint16_t aDelay,
|
||||
const uint8_t * aExtraTlvs,
|
||||
uint8_t aExtraTlvsLength)
|
||||
{
|
||||
OT_UNUSED_VARIABLE(aExtraTlvs);
|
||||
OT_UNUSED_VARIABLE(aExtraTlvsLength);
|
||||
|
||||
otError error = OT_ERROR_NONE;
|
||||
Message *message;
|
||||
|
||||
@@ -2046,6 +2051,11 @@ otError Mle::SendDataRequest(const Ip6::Address &aDestination,
|
||||
SuccessOrExit(error = AppendActiveTimestamp(*message));
|
||||
SuccessOrExit(error = AppendPendingTimestamp(*message));
|
||||
|
||||
if (aExtraTlvs != nullptr && aExtraTlvsLength > 0)
|
||||
{
|
||||
SuccessOrExit(error = message->Append(aExtraTlvs, aExtraTlvsLength));
|
||||
}
|
||||
|
||||
if (aDelay)
|
||||
{
|
||||
SuccessOrExit(error = AddDelayedResponse(*message, aDestination, aDelay));
|
||||
@@ -2801,11 +2811,23 @@ exit:
|
||||
void Mle::HandleDataResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, const Neighbor *aNeighbor)
|
||||
{
|
||||
otError error;
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
uint16_t metricsReportValueOffset;
|
||||
uint16_t length;
|
||||
#endif
|
||||
|
||||
Log(kMessageReceive, kTypeDataResponse, aMessageInfo.GetPeerAddr());
|
||||
|
||||
VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = OT_ERROR_SECURITY);
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
if (Tlv::FindTlvValueOffset(aMessage, Tlv::kLinkMetricsReport, metricsReportValueOffset, length) == OT_ERROR_NONE)
|
||||
{
|
||||
Get<LinkMetrics>().HandleLinkMetricsReport(aMessage, metricsReportValueOffset, length,
|
||||
aMessageInfo.GetPeerAddr());
|
||||
}
|
||||
#endif
|
||||
|
||||
error = HandleLeaderData(aMessage, aMessageInfo);
|
||||
|
||||
if (mDataRequestState == kDataRequestNone && !IsRxOnWhenIdle())
|
||||
|
||||
+13
-5
@@ -43,6 +43,7 @@
|
||||
#include "meshcop/joiner_router.hpp"
|
||||
#include "meshcop/meshcop.hpp"
|
||||
#include "net/udp6.hpp"
|
||||
#include "thread/link_metrics.hpp"
|
||||
#include "thread/mle_tlvs.hpp"
|
||||
#include "thread/mle_types.hpp"
|
||||
#include "thread/neighbor_table.hpp"
|
||||
@@ -92,6 +93,9 @@ class Mle : public InstanceLocator
|
||||
{
|
||||
friend class DiscoverScanner;
|
||||
friend class ot::Notifier;
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
friend class ot::LinkMetrics;
|
||||
#endif
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -1288,10 +1292,12 @@ protected:
|
||||
/**
|
||||
* This method generates an MLE Data Request message.
|
||||
*
|
||||
* @param[in] aDestination A reference to the IPv6 address of the destination.
|
||||
* @param[in] aTlvs A pointer to requested TLV types.
|
||||
* @param[in] aTlvsLength The number of TLV types in @p aTlvs.
|
||||
* @param[in] aDelay Delay in milliseconds before the Data Request message is sent.
|
||||
* @param[in] aDestination A reference to the IPv6 address of the destination.
|
||||
* @param[in] aTlvs A pointer to requested TLV types.
|
||||
* @param[in] aTlvsLength The number of TLV types in @p aTlvs.
|
||||
* @param[in] aDelay Delay in milliseconds before the Data Request message is sent.
|
||||
* @param[in] aExtraTlvs A pointer to extra TLVs.
|
||||
* @param[in] aExtraTlvsLength Length of extra TLVs.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Successfully generated an MLE Data Request message.
|
||||
* @retval OT_ERROR_NO_BUFS Insufficient buffers to generate the MLE Data Request message.
|
||||
@@ -1300,7 +1306,9 @@ protected:
|
||||
otError SendDataRequest(const Ip6::Address &aDestination,
|
||||
const uint8_t * aTlvs,
|
||||
uint8_t aTlvsLength,
|
||||
uint16_t aDelay);
|
||||
uint16_t aDelay,
|
||||
const uint8_t * aExtraTlvs = nullptr,
|
||||
uint8_t aExtraTlvsLength = 0);
|
||||
|
||||
/**
|
||||
* This method generates an MLE Child Update Request message.
|
||||
|
||||
@@ -2723,7 +2723,7 @@ void MleRouter::HandleDataRequest(const Message & aMessage,
|
||||
tlvs[numTlvs++] = Tlv::kPendingDataset;
|
||||
}
|
||||
|
||||
SendDataResponse(aMessageInfo.GetPeerAddr(), tlvs, numTlvs, 0);
|
||||
SendDataResponse(aMessageInfo.GetPeerAddr(), tlvs, numTlvs, 0, &aMessage);
|
||||
|
||||
exit:
|
||||
LogProcessError(kTypeDataRequest, error);
|
||||
@@ -3210,8 +3210,11 @@ exit:
|
||||
void MleRouter::SendDataResponse(const Ip6::Address &aDestination,
|
||||
const uint8_t * aTlvs,
|
||||
uint8_t aTlvsLength,
|
||||
uint16_t aDelay)
|
||||
uint16_t aDelay,
|
||||
const Message * aRequestMessage)
|
||||
{
|
||||
OT_UNUSED_VARIABLE(aRequestMessage);
|
||||
|
||||
otError error = OT_ERROR_NONE;
|
||||
Message * message = nullptr;
|
||||
Neighbor *neighbor;
|
||||
@@ -3248,6 +3251,13 @@ void MleRouter::SendDataResponse(const Ip6::Address &aDestination,
|
||||
case Tlv::kPendingDataset:
|
||||
SuccessOrExit(error = AppendPendingDataset(*message));
|
||||
break;
|
||||
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
case Tlv::kLinkMetricsReport:
|
||||
OT_ASSERT(aRequestMessage != nullptr);
|
||||
SuccessOrExit(error = Get<LinkMetrics>().AppendLinkMetricsReport(*message, *aRequestMessage));
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -615,7 +615,11 @@ private:
|
||||
const uint8_t * aTlvs,
|
||||
uint8_t aTlvsLength,
|
||||
const Challenge & aChallenge);
|
||||
void SendDataResponse(const Ip6::Address &aDestination, const uint8_t *aTlvs, uint8_t aTlvsLength, uint16_t aDelay);
|
||||
void SendDataResponse(const Ip6::Address &aDestination,
|
||||
const uint8_t * aTlvs,
|
||||
uint8_t aTlvsLength,
|
||||
uint16_t aDelay,
|
||||
const Message * aRequestMessage = nullptr);
|
||||
otError SendDiscoveryResponse(const Ip6::Address &aDestination, uint16_t aPanId);
|
||||
|
||||
void SetStateRouter(uint16_t aRloc16);
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
#include "common/tlvs.hpp"
|
||||
#include "meshcop/timestamp.hpp"
|
||||
#include "net/ip6_address.hpp"
|
||||
#include "thread/link_metrics_tlvs.hpp"
|
||||
#include "thread/mle_types.hpp"
|
||||
|
||||
namespace ot {
|
||||
@@ -103,6 +104,8 @@ public:
|
||||
kDiscovery = 26, ///< Thread Discovery TLV
|
||||
kCslChannel = 80, ///< CSL Channel TLV
|
||||
kCslTimeout = 85, ///< CSL Timeout TLV
|
||||
kLinkMetricsQuery = 87, ///< Link Metrics Query TLV
|
||||
kLinkMetricsReport = 89, ///< Link Metrics Report TLV
|
||||
|
||||
/**
|
||||
* Applicable/Required only when time synchronization service
|
||||
|
||||
@@ -126,6 +126,9 @@ ThreadNetif::ThreadNetif(Instance &aInstance)
|
||||
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
||||
, mTimeSync(aInstance)
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
, mLinkMetrics(aInstance)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
#include "thread/discover_scanner.hpp"
|
||||
#include "thread/energy_scan_server.hpp"
|
||||
#include "thread/key_manager.hpp"
|
||||
#include "thread/link_metrics.hpp"
|
||||
#include "thread/mesh_forwarder.hpp"
|
||||
#include "thread/mle.hpp"
|
||||
#include "thread/mle_router.hpp"
|
||||
@@ -266,6 +267,9 @@ private:
|
||||
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
|
||||
TimeSync mTimeSync;
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
|
||||
LinkMetrics mLinkMetrics;
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -257,6 +257,8 @@ def create_default_mle_tlvs_factories():
|
||||
mle.TlvType.TIME_REQUEST: mle.TimeRequestFactory(),
|
||||
mle.TlvType.TIME_PARAMETER: mle.TimeParameterFactory(),
|
||||
mle.TlvType.THREAD_DISCOVERY: create_default_mle_tlv_thread_discovery_factory(),
|
||||
mle.TlvType.LINK_METRICS_QUERY: mle.LinkMetricsQueryFactory(),
|
||||
mle.TlvType.LINK_METRICS_REPORT: mle.LinkMetricsReportFactory(),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -89,6 +89,8 @@ class TlvType(IntEnum):
|
||||
THREAD_DISCOVERY = 26
|
||||
CSL_CHANNEL = 80
|
||||
CSL_SYNCHRONIZED_TIMEOUT = 85
|
||||
LINK_METRICS_QUERY = 87
|
||||
LINK_METRICS_REPORT = 89
|
||||
TIME_REQUEST = 252
|
||||
TIME_PARAMETER = 253
|
||||
|
||||
@@ -1102,6 +1104,32 @@ class TimeParameterFactory:
|
||||
return TimeParameter()
|
||||
|
||||
|
||||
class LinkMetricsQuery:
|
||||
# TODO: Not implemented yet
|
||||
|
||||
def __init__(self):
|
||||
print("LinkMetricsQuery is not implemented yet.")
|
||||
|
||||
|
||||
class LinkMetricsQueryFactory:
|
||||
|
||||
def parse(self, data, message_info):
|
||||
return LinkMetricsQuery()
|
||||
|
||||
|
||||
class LinkMetricsReport:
|
||||
# TODO: Not implemented yet
|
||||
|
||||
def __init__(self):
|
||||
print("LinkMetricsReport is not implemented yet.")
|
||||
|
||||
|
||||
class LinkMetricsReportFactory:
|
||||
|
||||
def parse(self, data, message_info):
|
||||
return LinkMetricsReport()
|
||||
|
||||
|
||||
class MleCommand(object):
|
||||
|
||||
def __init__(self, _type, tlvs):
|
||||
|
||||
@@ -1866,6 +1866,11 @@ 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)
|
||||
self.send_command(cmd)
|
||||
self._expect('Done')
|
||||
|
||||
def send_address_notification(self, dst: str, target: str, mliid: str):
|
||||
cmd = f'fake /a/an {dst} {target} {mliid}'
|
||||
self.send_command(cmd)
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2020, The OpenThread Authors.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
import unittest
|
||||
|
||||
import mle
|
||||
import thread_cert
|
||||
|
||||
LEADER = 1
|
||||
SSED_1 = 2
|
||||
|
||||
CSL_PERIOD = 500 * 6.25 # 500ms
|
||||
CSL_TIMEOUT = 30 # 30s
|
||||
|
||||
|
||||
class SSED_SingleProbe(thread_cert.TestCase):
|
||||
TOPOLOGY = {
|
||||
LEADER: {
|
||||
'version': '1.2',
|
||||
},
|
||||
SSED_1: {
|
||||
'version': '1.2',
|
||||
'mode': 's',
|
||||
},
|
||||
}
|
||||
"""All nodes are created with default configurations"""
|
||||
|
||||
def test(self):
|
||||
|
||||
self.nodes[SSED_1].set_csl_period(CSL_PERIOD)
|
||||
self.nodes[SSED_1].set_csl_timeout(CSL_TIMEOUT)
|
||||
|
||||
self.nodes[LEADER].start()
|
||||
self.simulator.go(5)
|
||||
self.assertEqual(self.nodes[LEADER].get_state(), 'leader')
|
||||
|
||||
self.nodes[SSED_1].start()
|
||||
self.simulator.go(7)
|
||||
self.assertEqual(self.nodes[SSED_1].get_state(), 'child')
|
||||
|
||||
leader_addr = self.nodes[LEADER].get_linklocal()
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
leader_messages = self.simulator.get_messages_sent_by(LEADER)
|
||||
msg = leader_messages.next_mle_message(mle.CommandType.DATA_RESPONSE)
|
||||
msg.assertMleMessageContainsTlv(mle.LinkMetricsReport)
|
||||
|
||||
self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc()))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user