From 77d3af396c8bace8a1a6c9204c1a663ef751e9e6 Mon Sep 17 00:00:00 2001 From: Piotr Koziar <44554861+piotrkoziar@users.noreply.github.com> Date: Tue, 6 Apr 2021 16:42:46 +0200 Subject: [PATCH] [spinel] handle link metrics (#6228) This commit allows the spinel interface to deal with link metrics. There are 7 new properties introduced: - THREAD_LINK_METRICS_QUERY - THREAD_LINK_METRICS_QUERY_RESULT - THREAD_LINK_METRICS_PROBE - THREAD_LINK_METRICS_MGMT_ENH_ACK - THREAD_LINK_METRICS_MGMT_ENH_ACK_IE - THREAD_LINK_METRICS_MGMT_FORWARD - THREAD_LINK_METRICS_MGMT_RESPONSE --- src/lib/spinel/spinel.c | 7 + src/lib/spinel/spinel.h | 196 +++++++++++++++++++++++++ src/ncp/ncp_base.cpp | 12 ++ src/ncp/ncp_base.hpp | 30 ++++ src/ncp/ncp_base_dispatcher.cpp | 14 +- src/ncp/ncp_base_mtd.cpp | 249 ++++++++++++++++++++++++++++++++ 6 files changed, 504 insertions(+), 4 deletions(-) diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c index fcc5e95bf..12cb64ac8 100644 --- a/src/lib/spinel/spinel.c +++ b/src/lib/spinel/spinel.c @@ -1344,6 +1344,13 @@ const char *spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) {SPINEL_PROP_THREAD_CSL_TIMEOUT, "THREAD_CSL_TIMEOUT"}, {SPINEL_PROP_THREAD_CSL_CHANNEL, "THREAD_CSL_CHANNEL"}, {SPINEL_PROP_THREAD_DOMAIN_NAME, "THREAD_DOMAIN_NAME"}, + {SPINEL_PROP_THREAD_LINK_METRICS_QUERY, "THREAD_LINK_METRICS_QUERY"}, + {SPINEL_PROP_THREAD_LINK_METRICS_QUERY_RESULT, "THREAD_LINK_METRICS_QUERY_RESULT"}, + {SPINEL_PROP_THREAD_LINK_METRICS_PROBE, "THREAD_LINK_METRICS_PROBE"}, + {SPINEL_PROP_THREAD_LINK_METRICS_MGMT_ENH_ACK, "THREAD_LINK_METRICS_MGMT_ENH_ACK"}, + {SPINEL_PROP_THREAD_LINK_METRICS_MGMT_ENH_ACK_IE, "THREAD_LINK_METRICS_MGMT_ENH_ACK_IE"}, + {SPINEL_PROP_THREAD_LINK_METRICS_MGMT_FORWARD, "THREAD_LINK_METRICS_MGMT_FORWARD"}, + {SPINEL_PROP_THREAD_LINK_METRICS_MGMT_RESPONSE, "THREAD_LINK_METRICS_MGMT_RESPONSE"}, {SPINEL_PROP_THREAD_MLR_REQUEST, "THREAD_MLR_REQUEST"}, {SPINEL_PROP_THREAD_MLR_RESPONSE, "THREAD_MLR_RESPONSE"}, {SPINEL_PROP_THREAD_BACKBONE_ROUTER_PRIMARY, "THREAD_BACKBONE_ROUTER_PRIMARY"}, diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index db1b6169e..91c192793 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -466,6 +466,8 @@ enum SPINEL_STATUS_ALREADY = 19, ///< The operation is already in progress. SPINEL_STATUS_ITEM_NOT_FOUND = 20, ///< The given item could not be found. SPINEL_STATUS_INVALID_COMMAND_FOR_PROP = 21, ///< The given command cannot be performed on this property. + SPINEL_STATUS_UNKNOWN_NEIGHBOR = 22, ///< The neighbor is unknown. + SPINEL_STATUS_NOT_CAPABLE = 23, ///< The target is not capable of handling requested operation. SPINEL_STATUS_RESPONSE_TIMEOUT = 24, ///< No response received from remote node SPINEL_STATUS_JOIN__BEGIN = 104, @@ -725,6 +727,43 @@ enum SPINEL_RADIO_LINK_TREL_UDP6 = 1, }; +// Statuses that can be received as a result of: +// @ref SPINEL_PROP_THREAD_LINK_METRICS_QUERY +// @ref SPINEL_PROP_THREAD_LINK_METRICS_MGMT_ENH_ACK +// @ref SPINEL_PROP_THREAD_LINK_METRICS_MGMT_FORWARD +enum +{ + SPINEL_LINK_METRICS_STATUS_SUCCESS = 0, + SPINEL_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES = 1, + SPINEL_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED = 2, + SPINEL_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED = 3, + SPINEL_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED = 4, + SPINEL_LINK_METRICS_STATUS_OTHER_ERROR = 254 +}; + +// Metric ids used for: +// @ref SPINEL_PROP_THREAD_LINK_METRICS_QUERY +// @ref SPINEL_PROP_THREAD_LINK_METRICS_QUERY_RESULT +// @ref SPINEL_PROP_THREAD_LINK_METRICS_MGMT_ENH_ACK +// @ref SPINEL_PROP_THREAD_LINK_METRICS_MGMT_FORWARD +enum +{ + SPINEL_THREAD_LINK_METRIC_PDU_COUNT = (1 << 0), + SPINEL_THREAD_LINK_METRIC_LQI = (1 << 1), + SPINEL_THREAD_LINK_METRIC_LINK_MARGIN = (1 << 2), + SPINEL_THREAD_LINK_METRIC_RSSI = (1 << 3), +}; + +// Frame types used for: +// @ref SPINEL_PROP_THREAD_LINK_METRICS_MGMT_FORWARD +enum +{ + SPINEL_THREAD_FRAME_TYPE_MLE_LINK_PROBE = (1 << 0), + SPINEL_THREAD_FRAME_TYPE_MAC_DATA = (1 << 1), + SPINEL_THREAD_FRAME_TYPE_MAC_DATA_REQUEST = (1 << 2), + SPINEL_THREAD_FRAME_TYPE_MAC_ACK = (1 << 3), +}; + // Parameter ids used for: // @ref SPINEL_PROP_THREAD_MLR_REQUEST enum @@ -1216,6 +1255,7 @@ enum SPINEL_CAP_THREAD_BORDER_ROUTER = (SPINEL_CAP_THREAD__BEGIN + 4), SPINEL_CAP_THREAD_SERVICE = (SPINEL_CAP_THREAD__BEGIN + 5), SPINEL_CAP_THREAD_CSL_RECEIVER = (SPINEL_CAP_THREAD__BEGIN + 6), + SPINEL_CAP_THREAD_LINK_METRICS = (SPINEL_CAP_THREAD__BEGIN + 7), SPINEL_CAP_THREAD_BACKBONE_ROUTER = (SPINEL_CAP_THREAD__BEGIN + 8), SPINEL_CAP_THREAD__END = 1152, @@ -2964,6 +3004,160 @@ enum */ SPINEL_PROP_THREAD_DOMAIN_NAME = SPINEL_PROP_THREAD_EXT__BEGIN + 44, + /// Link metrics query + /** Format: `6CC` - Write-Only + * + * Required capability: `SPINEL_CAP_THREAD_LINK_METRICS` + * + * `6` : IPv6 destination address + * `C` : Series id (0 for Single Probe) + * `C` : List of requested metric ids encoded as bit fields in single byte + * + * +---------------+----+ + * | Metric | Id | + * +---------------+----+ + * | Received PDUs | 0 | + * | LQI | 1 | + * | Link margin | 2 | + * | RSSI | 3 | + * +---------------+----+ + * + * If the query succeeds, the NCP will send a result to the Host using + * @ref SPINEL_PROP_THREAD_LINK_METRICS_QUERY_RESULT. + * + */ + SPINEL_PROP_THREAD_LINK_METRICS_QUERY = SPINEL_PROP_THREAD_EXT__BEGIN + 45, + + /// Link metrics query result + /** Format: `6Ct(A(t(CD)))` - Unsolicited notifications only + * + * Required capability: `SPINEL_CAP_THREAD_LINK_METRICS` + * + * `6` : IPv6 destination address + * `C` : Status + * `t(A(t(CD)))` : Array of structs encoded as following: + * `C` : Metric id + * `D` : Metric value + * + * +---------------+----+----------------+ + * | Metric | Id | Value format | + * +---------------+----+----------------+ + * | Received PDUs | 0 | `L` (uint32_t) | + * | LQI | 1 | `C` (uint8_t) | + * | Link margin | 2 | `C` (uint8_t) | + * | RSSI | 3 | `c` (int8_t) | + * +---------------+----+----------------+ + * + */ + SPINEL_PROP_THREAD_LINK_METRICS_QUERY_RESULT = SPINEL_PROP_THREAD_EXT__BEGIN + 46, + + /// Link metrics probe + /** Format `6CC` - Write only + * Required capability: `SPINEL_CAP_THREAD_LINK_METRICS` + * + * Send a MLE Link Probe message to the peer. + * + * `6` : IPv6 destination address + * `C` : The Series ID for which this Probe message targets at + * `C` : The length of the Probe message, valid range: [0, 64] + * + */ + SPINEL_PROP_THREAD_LINK_METRICS_PROBE = SPINEL_PROP_THREAD_EXT__BEGIN + 47, + + /// Link metrics Enhanced-ACK Based Probing management + /** Format: 6Cd - Write only + * + * Required capability: `SPINEL_CAP_THREAD_LINK_METRICS` + * + * `6` : IPv6 destination address + * `C` : Indicate whether to register or clear the probing. `0` - clear, `1` - register + * `C` : List of requested metric ids encoded as bit fields in single byte + * + * +---------------+----+ + * | Metric | Id | + * +---------------+----+ + * | LQI | 1 | + * | Link margin | 2 | + * | RSSI | 3 | + * +---------------+----+ + * + * Result of configuration is reported asynchronously to the Host using the + * @ref SPINEL_PROP_THREAD_LINK_METRICS_MGMT_RESPONSE. + * + * Whenever Enh-ACK IE report is received it is passed to the Host using the + * @ref SPINEL_PROP_THREAD_LINK_METRICS_MGMT_ENH_ACK_IE property. + * + */ + SPINEL_PROP_THREAD_LINK_METRICS_MGMT_ENH_ACK = SPINEL_PROP_THREAD_EXT__BEGIN + 48, + + /// Link metrics Enhanced-ACK Based Probing IE report + /** Format: SEA(t(CD)) - Unsolicited notifications only + * + * Required capability: `SPINEL_CAP_THREAD_LINK_METRICS` + * + * `S` : Short address of the Probing Subject + * `E` : Extended address of the Probing Subject + * `t(A(t(CD)))` : Struct that contains array of structs encoded as following: + * `C` : Metric id + * `D` : Metric value + * + * +---------------+----+----------------+ + * | Metric | Id | Value format | + * +---------------+----+----------------+ + * | LQI | 1 | `C` (uint8_t) | + * | Link margin | 2 | `C` (uint8_t) | + * | RSSI | 3 | `c` (int8_t) | + * +---------------+----+----------------+ + * + */ + SPINEL_PROP_THREAD_LINK_METRICS_MGMT_ENH_ACK_IE = SPINEL_PROP_THREAD_EXT__BEGIN + 49, + + /// Link metrics Forward Tracking Series management + /** Format: 6CCC - Write only + * + * Required capability: `SPINEL_CAP_THREAD_LINK_METRICS` + * + * `6` : IPv6 destination address + * `C` : Series id + * `C` : Tracked frame types encoded as bit fields in single byte, if equal to zero, + * accounting is stopped and a series is removed + * `C` : Requested metric ids encoded as bit fields in single byte + * + * +------------------+----+ + * | Frame type | Id | + * +------------------+----+ + * | MLE Link Probe | 0 | + * | MAC Data | 1 | + * | MAC Data Request | 2 | + * | MAC ACK | 3 | + * +------------------+----+ + * + * +---------------+----+ + * | Metric | Id | + * +---------------+----+ + * | Received PDUs | 0 | + * | LQI | 1 | + * | Link margin | 2 | + * | RSSI | 3 | + * +---------------+----+ + * + * Result of configuration is reported asynchronously to the Host using the + * @ref SPINEL_PROP_THREAD_LINK_METRICS_MGMT_RESPONSE. + * + */ + SPINEL_PROP_THREAD_LINK_METRICS_MGMT_FORWARD = SPINEL_PROP_THREAD_EXT__BEGIN + 50, + + /// Link metrics management response + /** Format: 6C - Unsolicited notifications only + * + * Required capability: `SPINEL_CAP_THREAD_LINK_METRICS` + * + * `6` : IPv6 source address + * `C` : Received status + * + */ + SPINEL_PROP_THREAD_LINK_METRICS_MGMT_RESPONSE = SPINEL_PROP_THREAD_EXT__BEGIN + 51, + /// Multicast Listeners Register Request /** Format `t(A(6))A(t(CD))` - Write-only * Required capability: `SPINEL_CAP_NET_THREAD_1_2` @@ -4746,6 +4940,8 @@ SPINEL_API_EXTERN const char *spinel_capability_to_cstr(spinel_capability_t capa SPINEL_API_EXTERN const char *spinel_radio_link_to_cstr(uint32_t radio); +SPINEL_API_EXTERN const char *spinel_link_metrics_status_to_cstr(uint8_t status); + // ---------------------------------------------------------------------------- #if defined(__cplusplus) diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index a03d4519f..e89476481 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -135,6 +135,14 @@ spinel_status_t NcpBase::ThreadErrorToSpinelStatus(otError aError) ret = SPINEL_STATUS_ITEM_NOT_FOUND; break; + case OT_ERROR_UNKNOWN_NEIGHBOR: + ret = SPINEL_STATUS_UNKNOWN_NEIGHBOR; + break; + + case OT_ERROR_NOT_CAPABLE: + ret = SPINEL_STATUS_NOT_CAPABLE; + break; + case OT_ERROR_RESPONSE_TIMEOUT: ret = SPINEL_STATUS_RESPONSE_TIMEOUT; break; @@ -1926,6 +1934,10 @@ template <> otError NcpBase::HandlePropertyGet(void) SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_SRP_CLIENT)); #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_LINK_METRICS)); +#endif + #if OPENTHREAD_CONFIG_DUA_ENABLE SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_DUA)); #endif diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp index a9a227340..165e0fded 100644 --- a/src/ncp/ncp_base.hpp +++ b/src/ncp/ncp_base.hpp @@ -337,6 +337,30 @@ protected: void HandleJoinerCallback(otError aError); #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + static void HandleLinkMetricsReport_Jump(const otIp6Address * aSource, + const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus, + void * aContext); + + void HandleLinkMetricsReport(const otIp6Address * aSource, + const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus); + + static void HandleLinkMetricsMgmtResponse_Jump(const otIp6Address *aSource, uint8_t aStatus, void *aContext); + + void HandleLinkMetricsMgmtResponse(const otIp6Address *aSource, uint8_t aStatus); + + static void HandleLinkMetricsEnhAckProbingIeReport_Jump(otShortAddress aShortAddress, + const otExtAddress * aExtAddress, + const otLinkMetricsValues *aMetricsValues, + void * aContext); + + void HandleLinkMetricsEnhAckProbingIeReport(otShortAddress aShortAddress, + const otExtAddress * aExtAddress, + const otLinkMetricsValues *aMetricsValues); +#endif + static void HandleMlrRegResult_Jump(void * aContext, otError aError, uint8_t aMlrStatus, @@ -364,6 +388,12 @@ protected: otError EncodeChildInfo(const otChildInfo &aChildInfo); #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + otError DecodeLinkMetrics(otLinkMetrics *aMetrics, bool aAllowPduCount); + + otError EncodeLinkMetricsValues(const otLinkMetricsValues *aMetricsValues); +#endif + #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE static void HandleUdpForwardStream(otMessage * aMessage, uint16_t aPeerPort, diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp index 8a583e794..b85a04c2f 100644 --- a/src/ncp/ncp_base_dispatcher.cpp +++ b/src/ncp/ncp_base_dispatcher.cpp @@ -553,16 +553,22 @@ NcpBase::PropertyHandler NcpBase::FindSetPropertyHandler(spinel_prop_key_t aKey) OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_CSL_TIMEOUT), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_CSL_CHANNEL), #endif -#if OPENTHREAD_FTD -#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) +#if OPENTHREAD_FTD && (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_DOMAIN_NAME), #endif -#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_LINK_METRICS_QUERY), + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_LINK_METRICS_PROBE), + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_LINK_METRICS_MGMT_ENH_ACK), + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_LINK_METRICS_MGMT_FORWARD), +#endif +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_MLR_REQUEST), #endif -#if OPENTHREAD_CONFIG_DUA_ENABLE +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_DUA_ENABLE OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_DUA_ID), #endif +#if OPENTHREAD_FTD #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_STATE), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_CONFIG), diff --git a/src/ncp/ncp_base_mtd.cpp b/src/ncp/ncp_base_mtd.cpp index b4ff507a1..f4f9d9ac3 100644 --- a/src/ncp/ncp_base_mtd.cpp +++ b/src/ncp/ncp_base_mtd.cpp @@ -209,6 +209,84 @@ exit: return error; } +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +otError NcpBase::DecodeLinkMetrics(otLinkMetrics *aMetrics, bool aAllowPduCount) +{ + otError error = OT_ERROR_NONE; + uint8_t metrics = 0; + + SuccessOrExit(error = mDecoder.ReadUint8(metrics)); + + if (metrics & SPINEL_THREAD_LINK_METRIC_PDU_COUNT) + { + VerifyOrExit(aAllowPduCount, error = OT_ERROR_INVALID_ARGS); + aMetrics->mPduCount = true; + } + + if (metrics & SPINEL_THREAD_LINK_METRIC_LQI) + { + aMetrics->mLqi = true; + } + + if (metrics & SPINEL_THREAD_LINK_METRIC_LINK_MARGIN) + { + aMetrics->mLinkMargin = true; + } + + if (metrics & SPINEL_THREAD_LINK_METRIC_RSSI) + { + aMetrics->mRssi = true; + } + +exit: + return error; +} + +otError NcpBase::EncodeLinkMetricsValues(const otLinkMetricsValues *aMetricsValues) +{ + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = mEncoder.OpenStruct()); + + if (aMetricsValues->mMetrics.mPduCount) + { + SuccessOrExit(error = mEncoder.OpenStruct()); + SuccessOrExit(error = mEncoder.WriteUint8(SPINEL_THREAD_LINK_METRIC_PDU_COUNT)); + SuccessOrExit(error = mEncoder.WriteUint32(aMetricsValues->mPduCountValue)); + SuccessOrExit(error = mEncoder.CloseStruct()); + } + + if (aMetricsValues->mMetrics.mLqi) + { + SuccessOrExit(error = mEncoder.OpenStruct()); + SuccessOrExit(error = mEncoder.WriteUint8(SPINEL_THREAD_LINK_METRIC_LQI)); + SuccessOrExit(error = mEncoder.WriteUint8(aMetricsValues->mLqiValue)); + SuccessOrExit(error = mEncoder.CloseStruct()); + } + + if (aMetricsValues->mMetrics.mLinkMargin) + { + SuccessOrExit(error = mEncoder.OpenStruct()); + SuccessOrExit(error = mEncoder.WriteUint8(SPINEL_THREAD_LINK_METRIC_LINK_MARGIN)); + SuccessOrExit(error = mEncoder.WriteUint8(aMetricsValues->mLinkMarginValue)); + SuccessOrExit(error = mEncoder.CloseStruct()); + } + + if (aMetricsValues->mMetrics.mRssi) + { + SuccessOrExit(error = mEncoder.OpenStruct()); + SuccessOrExit(error = mEncoder.WriteUint8(SPINEL_THREAD_LINK_METRIC_RSSI)); + SuccessOrExit(error = mEncoder.WriteInt8(aMetricsValues->mRssiValue)); + SuccessOrExit(error = mEncoder.CloseStruct()); + } + + SuccessOrExit(error = mEncoder.CloseStruct()); + +exit: + return error; +} +#endif + #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE template <> otError NcpBase::HandlePropertySet(void) { @@ -3039,6 +3117,105 @@ exit: } #endif // OPENTHREAD_CONFIG_MAC_FILTER_ENABLE +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +template <> otError NcpBase::HandlePropertySet(void) +{ + otError error = OT_ERROR_NONE; + struct otIp6Address address; + uint8_t seriesId; + otLinkMetrics linkMetrics = {}; + + SuccessOrExit(error = mDecoder.ReadIp6Address(address)); + SuccessOrExit(error = mDecoder.ReadUint8(seriesId)); + SuccessOrExit(error = DecodeLinkMetrics(&linkMetrics, true)); + + error = + otLinkMetricsQuery(mInstance, &address, seriesId, &linkMetrics, &NcpBase::HandleLinkMetricsReport_Jump, this); + +exit: + return error; +} + +template <> otError NcpBase::HandlePropertySet(void) +{ + otError error = OT_ERROR_NONE; + struct otIp6Address address; + uint8_t seriesId; + uint8_t length; + + SuccessOrExit(error = mDecoder.ReadIp6Address(address)); + SuccessOrExit(error = mDecoder.ReadUint8(seriesId)); + SuccessOrExit(error = mDecoder.ReadUint8(length)); + + error = otLinkMetricsSendLinkProbe(mInstance, &address, seriesId, length); + +exit: + return error; +} + +template <> otError NcpBase::HandlePropertySet(void) +{ + otError error = OT_ERROR_NONE; + struct otIp6Address address; + uint8_t controlFlags; + otLinkMetrics linkMetrics = {}; + + SuccessOrExit(error = mDecoder.ReadIp6Address(address)); + SuccessOrExit(error = mDecoder.ReadUint8(controlFlags)); + SuccessOrExit(error = DecodeLinkMetrics(&linkMetrics, false)); + + error = otLinkMetricsConfigEnhAckProbing(mInstance, &address, static_cast(controlFlags), + controlFlags ? &linkMetrics : nullptr, + &NcpBase::HandleLinkMetricsMgmtResponse_Jump, this, + &NcpBase::HandleLinkMetricsEnhAckProbingIeReport_Jump, this); + +exit: + return error; +} + +template <> otError NcpBase::HandlePropertySet(void) +{ + otError error = OT_ERROR_NONE; + struct otIp6Address address; + uint8_t seriesId; + uint8_t types; + otLinkMetrics linkMetrics = {}; + otLinkMetricsSeriesFlags seriesFlags = {}; + + SuccessOrExit(error = mDecoder.ReadIp6Address(address)); + SuccessOrExit(error = mDecoder.ReadUint8(seriesId)); + SuccessOrExit(error = mDecoder.ReadUint8(types)); + + SuccessOrExit(error = DecodeLinkMetrics(&linkMetrics, true)); + + if (types & SPINEL_THREAD_FRAME_TYPE_MLE_LINK_PROBE) + { + seriesFlags.mLinkProbe = true; + } + + if (types & SPINEL_THREAD_FRAME_TYPE_MAC_DATA) + { + seriesFlags.mMacData = true; + } + + if (types & SPINEL_THREAD_FRAME_TYPE_MAC_DATA_REQUEST) + { + seriesFlags.mMacDataRequest = true; + } + + if (types & SPINEL_THREAD_FRAME_TYPE_MAC_ACK) + { + seriesFlags.mMacAck = true; + } + + error = otLinkMetricsConfigForwardTrackingSeries(mInstance, &address, seriesId, seriesFlags, &linkMetrics, + &NcpBase::HandleLinkMetricsMgmtResponse_Jump, this); + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE + template <> otError NcpBase::HandlePropertyGet(void) { uint8_t numericMode; @@ -4125,6 +4302,78 @@ void NcpBase::HandleJoinerCallback(otError aError) } #endif +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE +void NcpBase::HandleLinkMetricsReport_Jump(const otIp6Address * aSource, + const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus, + void * aContext) +{ + static_cast(aContext)->HandleLinkMetricsReport(aSource, aMetricsValues, aStatus); +} + +void NcpBase::HandleLinkMetricsReport(const otIp6Address * aSource, + const otLinkMetricsValues *aMetricsValues, + uint8_t aStatus) +{ + SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS, + SPINEL_PROP_THREAD_LINK_METRICS_QUERY_RESULT)); + + SuccessOrExit(mEncoder.WriteIp6Address(*aSource)); + SuccessOrExit(mEncoder.WriteUint8(aStatus)); + SuccessOrExit(EncodeLinkMetricsValues(aMetricsValues)); + + SuccessOrExit(mEncoder.EndFrame()); + +exit: + return; +} + +void NcpBase::HandleLinkMetricsMgmtResponse_Jump(const otIp6Address *aSource, uint8_t aStatus, void *aContext) +{ + static_cast(aContext)->HandleLinkMetricsMgmtResponse(aSource, aStatus); +} + +void NcpBase::HandleLinkMetricsMgmtResponse(const otIp6Address *aSource, uint8_t aStatus) +{ + SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS, + SPINEL_PROP_THREAD_LINK_METRICS_MGMT_RESPONSE)); + + SuccessOrExit(mEncoder.WriteIp6Address(*aSource)); + SuccessOrExit(mEncoder.WriteUint8(aStatus)); + + SuccessOrExit(mEncoder.EndFrame()); + +exit: + return; +} + +void NcpBase::HandleLinkMetricsEnhAckProbingIeReport_Jump(otShortAddress aShortAddress, + const otExtAddress * aExtAddress, + const otLinkMetricsValues *aMetricsValues, + void * aContext) +{ + static_cast(aContext)->HandleLinkMetricsEnhAckProbingIeReport(aShortAddress, aExtAddress, + aMetricsValues); +} + +void NcpBase::HandleLinkMetricsEnhAckProbingIeReport(otShortAddress aShortAddress, + const otExtAddress * aExtAddress, + const otLinkMetricsValues *aMetricsValues) +{ + SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS, + SPINEL_PROP_THREAD_LINK_METRICS_MGMT_ENH_ACK_IE)); + + SuccessOrExit(mEncoder.WriteUint16(aShortAddress)); + SuccessOrExit(mEncoder.WriteEui64(*aExtAddress)); + SuccessOrExit(EncodeLinkMetricsValues(aMetricsValues)); + + SuccessOrExit(mEncoder.EndFrame()); + +exit: + return; +} +#endif + // ---------------------------------------------------------------------------- // MARK: Outbound Datagram Handling // ----------------------------------------------------------------------------