From 3ac4d4ac19305bd670eedaf3bc377bab11f0f748 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Thu, 5 Mar 2026 01:02:18 -0600 Subject: [PATCH] [nexus] implement link metrics support (#12621) This commit implements Link Metrics support in the nexus platform, similar to the simulation platform implementation. Key changes include: - Added 'LinkMetricsInfo' struct and management methods to 'Radio' in 'nexus_radio'. - Implemented 'otPlatRadioConfigureEnhAckProbing()' and Enhanced ACK IE generation in 'nexus_core' and 'nexus_radio'. - Enabled Link Metrics initiator and subject configuration flags in 'openthread-core-nexus-config.h'. - Fixed spelling of 'kRadioSensitivity' in 'nexus_radio'. - Added 'fflush(stdout)' to the logging utility in 'nexus_misc' to improve real-time log capture during tests. --- tests/nexus/openthread-core-nexus-config.h | 4 +- tests/nexus/platform/nexus_core.cpp | 23 ++++- tests/nexus/platform/nexus_core.hpp | 3 +- tests/nexus/platform/nexus_misc.cpp | 1 + tests/nexus/platform/nexus_radio.cpp | 102 ++++++++++++++++++++- tests/nexus/platform/nexus_radio.hpp | 31 ++++++- 6 files changed, 154 insertions(+), 10 deletions(-) diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h index 2a911913a..cd257c019 100644 --- a/tests/nexus/openthread-core-nexus-config.h +++ b/tests/nexus/openthread-core-nexus-config.h @@ -99,8 +99,8 @@ #define OPENTHREAD_CONFIG_MLE_DISCOVERY_SCAN_REQUEST_CALLBACK_ENABLE 1 #define OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH 1 #define OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD 10 -#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE 0 -#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE 0 +#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE 1 +#define OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE 1 #define OPENTHREAD_CONFIG_MLE_MAX_CHILDREN 128 #define OPENTHREAD_CONFIG_MULTICAST_DNS_AUTO_ENABLE_ON_INFRA_IF 1 #define OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE 1 diff --git a/tests/nexus/platform/nexus_core.cpp b/tests/nexus/platform/nexus_core.cpp index 29aa37711..806bb72ba 100644 --- a/tests/nexus/platform/nexus_core.cpp +++ b/tests/nexus/platform/nexus_core.cpp @@ -31,6 +31,7 @@ #include #include +#include "mac_frame.h" #include "nexus_node.hpp" namespace ot { @@ -391,7 +392,7 @@ void Core::ProcessRadio(Node &aNode) rxFrame.mInfo.mRxInfo.mTimestamp = mNow; rxFrame.mInfo.mRxInfo.mRssi = kDefaultRxRssi; - rxFrame.mInfo.mRxInfo.mLqi = 0; + rxFrame.mInfo.mRxInfo.mLqi = kDefaultRxLqi; if (matchesDst && !dstAddr.IsNone() && !dstAddr.IsBroadcast() && ackRequested) { @@ -443,6 +444,26 @@ void Core::ProcessRadio(Node &aNode) ackIeDataLength = otMacFrameGenerateCslIeTemplate(ackIeData); } #endif + +#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE + { + uint8_t linkMetricsData[OT_ENH_PROBING_IE_DATA_MAX_SIZE]; + uint8_t linkMetricsDataLen; + Mac::Address srcAddr; + + if (aNode.mRadio.mTxFrame.GetSrcAddr(srcAddr) == kErrorNone) + { + linkMetricsDataLen = ackNode->mRadio.GenerateEnhAckProbingData(srcAddr, kDefaultRxLqi, + kDefaultRxRssi, linkMetricsData); + + if (linkMetricsDataLen > 0) + { + ackIeDataLength += otMacFrameGenerateEnhAckProbingIe(ackIeData + ackIeDataLength, + linkMetricsData, linkMetricsDataLen); + } + } + } +#endif SuccessOrExit( ackFrame.GenerateEnhAck(rxFrame, (ackMode == kSendAckFramePending), ackIeData, ackIeDataLength)); SuccessOrExit(otMacFrameProcessTxSfd(&ackFrame, mNow, &ackNode->mRadio.mRadioContext)); diff --git a/tests/nexus/platform/nexus_core.hpp b/tests/nexus/platform/nexus_core.hpp index f848cd7a7..cf016a213 100644 --- a/tests/nexus/platform/nexus_core.hpp +++ b/tests/nexus/platform/nexus_core.hpp @@ -82,7 +82,8 @@ public: void MarkPendingAction(void) { mPendingAction = true; } private: - static constexpr int8_t kDefaultRxRssi = -20; + static constexpr int8_t kDefaultRxRssi = -20; + static constexpr uint8_t kDefaultRxLqi = 255; enum AckMode : uint8_t { diff --git a/tests/nexus/platform/nexus_misc.cpp b/tests/nexus/platform/nexus_misc.cpp index 4a6263e48..e9e17b75f 100644 --- a/tests/nexus/platform/nexus_misc.cpp +++ b/tests/nexus/platform/nexus_misc.cpp @@ -158,6 +158,7 @@ static void LogVarArgs(Node *aActiveNode, const char *aFormat, va_list aArgs) vprintf(aFormat, aArgs); printf("\n"); + fflush(stdout); } } // namespace Nexus diff --git a/tests/nexus/platform/nexus_radio.cpp b/tests/nexus/platform/nexus_radio.cpp index 950edeb2f..72a3db22f 100644 --- a/tests/nexus/platform/nexus_radio.cpp +++ b/tests/nexus/platform/nexus_radio.cpp @@ -48,7 +48,7 @@ otRadioCaps otPlatRadioGetCaps(otInstance *aInstance) int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); - return Radio::kRadioSensetivity; + return Radio::kRadioSensitivity; } void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) @@ -164,7 +164,7 @@ uint64_t otPlatRadioGetNow(otInstance *aInstance) int8_t otPlatRadioGetRssi(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); - return Radio::kRadioSensetivity; + return Radio::kRadioSensitivity; } void otPlatRadioEnableSrcMatch(otInstance *aInstance, bool aEnable) @@ -347,6 +347,14 @@ otError otPlatRadioAddCalibratedPower(otInstance *, uint8_t, int16_t, const uint return kErrorNotImplemented; } +otError otPlatRadioConfigureEnhAckProbing(otInstance *aInstance, + otLinkMetrics aLinkMetrics, + otShortAddress aShortAddress, + const otExtAddress *aExtAddress) +{ + return AsNode(aInstance).mRadio.ConfigureEnhAckProbing(aShortAddress, AsCoreTypePtr(aExtAddress), aLinkMetrics); +} + } // extern "C" //--------------------------------------------------------------------------------------------------------------------- @@ -363,6 +371,7 @@ Radio::Radio(void) { mExtAddress.Clear(); ClearAllBytes(mRadioContext); + mTxFrame.mInfo.mTxInfo.mIeInfo = &mTxIeInfo; } void Radio::Reset(void) @@ -377,7 +386,96 @@ void Radio::Reset(void) mExtAddress.Clear(); mSrcMatchShortEntries.Clear(); mSrcMatchExtEntries.Clear(); + mLinkMetricsEntries.Clear(); ClearAllBytes(mRadioContext); + mTxFrame.mInfo.mTxInfo.mIeInfo = &mTxIeInfo; +} + +Error Radio::ConfigureEnhAckProbing(Mac::ShortAddress aShortAddress, + const Mac::ExtAddress *aExtAddress, + otLinkMetrics aMetrics) +{ + Error error = kErrorNone; + LinkMetricsInfo *dataInfo = mLinkMetricsEntries.FindMatching(aShortAddress); + + if (!aMetrics.mPduCount && !aMetrics.mLqi && !aMetrics.mLinkMargin && !aMetrics.mRssi) // Remove entry + { + VerifyOrExit(dataInfo != nullptr, error = kErrorNotFound); + mLinkMetricsEntries.Remove(*dataInfo); + } + else + { + VerifyOrExit(aExtAddress != nullptr, error = kErrorInvalidArgs); + + if (dataInfo == nullptr) + { + dataInfo = mLinkMetricsEntries.PushBack(); + VerifyOrExit(dataInfo != nullptr, error = kErrorNoBufs); + } + + dataInfo->mShortAddress = aShortAddress; + dataInfo->mExtAddress = *aExtAddress; + dataInfo->mMetrics = aMetrics; + } + +exit: + return error; +} + +uint8_t Radio::GenerateEnhAckProbingData(const Mac::Address &aAddress, uint8_t aLqi, int8_t aRssi, uint8_t *aData) const +{ + uint8_t bytes = 0; + const LinkMetricsInfo *dataInfo = nullptr; + + if (aAddress.IsShort()) + { + dataInfo = mLinkMetricsEntries.FindMatching(aAddress.GetShort()); + } + else if (aAddress.IsExtended()) + { + dataInfo = mLinkMetricsEntries.FindMatching(aAddress.GetExtended()); + } + + VerifyOrExit(dataInfo != nullptr); + + if (dataInfo->mMetrics.mLqi) + { + aData[bytes++] = aLqi; + } + + if (dataInfo->mMetrics.mLinkMargin) + { + uint8_t linkMargin = ComputeLinkMargin(kRadioSensitivity, aRssi); + + // Linear scale Link Margin from [0, 130] to [0, 255] + if (linkMargin > kLinkMetricsScale) + { + linkMargin = kLinkMetricsScale; + } + + aData[bytes++] = static_cast(static_cast(linkMargin) * kLinkMetricsMax / kLinkMetricsScale); + } + + if (bytes < kEnhAckProbingDataMaxLen && dataInfo->mMetrics.mRssi) + { + int16_t rssi = aRssi; + + // Linear scale RSSI from [-130, 0] to [0, 255] + if (rssi > 0) + { + rssi = 0; + } + else if (rssi < -static_cast(kRssiOffset)) + { + rssi = -static_cast(kRssiOffset); + } + + aData[bytes++] = + static_cast((static_cast(rssi + kRssiOffset)) * kLinkMetricsMax / kLinkMetricsScale); + } + +exit: + return bytes; } bool Radio::CanReceiveOnChannel(uint8_t aChannel) const diff --git a/tests/nexus/platform/nexus_radio.hpp b/tests/nexus/platform/nexus_radio.hpp index 40e702d68..0c7dcef0e 100644 --- a/tests/nexus/platform/nexus_radio.hpp +++ b/tests/nexus/platform/nexus_radio.hpp @@ -39,10 +39,15 @@ struct Radio { static constexpr uint16_t kMaxFrameSize = OT_RADIO_FRAME_MAX_SIZE; - static constexpr uint16_t kMaxSrcMaatchShort = 80; - static constexpr uint16_t kMaxSrcMatchExt = 10; + static constexpr uint16_t kMaxSrcMatchShort = 80; + static constexpr uint16_t kMaxSrcMatchExt = 10; - static constexpr int8_t kRadioSensetivity = -100; + static constexpr int8_t kRadioSensitivity = -100; + + static constexpr uint8_t kEnhAckProbingDataMaxLen = 2; + static constexpr uint8_t kLinkMetricsMax = 255; + static constexpr uint8_t kLinkMetricsScale = 130; + static constexpr int16_t kRssiOffset = 130; using State = otRadioState; @@ -64,12 +69,27 @@ struct Radio uint8_t mPsduBuffer[kMaxFrameSize]; }; + struct LinkMetricsInfo + { + bool Matches(Mac::ShortAddress aShortAddress) const { return mShortAddress == aShortAddress; } + bool Matches(const Mac::ExtAddress &aExtAddress) const { return mExtAddress == aExtAddress; } + + Mac::ShortAddress mShortAddress; + Mac::ExtAddress mExtAddress; + otLinkMetrics mMetrics; + }; + Radio(void); void Reset(void); bool CanReceiveOnChannel(uint8_t aChannel) const; bool Matches(const Mac::Address &aAddress, Mac::PanId aPanId) const; bool HasFramePendingFor(const Mac::Address &aAddress) const; + Error ConfigureEnhAckProbing(Mac::ShortAddress aShortAddress, + const Mac::ExtAddress *aExtAddress, + otLinkMetrics aMetrics); + uint8_t GenerateEnhAckProbingData(const Mac::Address &aAddress, uint8_t aLqi, int8_t aRssi, uint8_t *aData) const; + State mState; bool mPromiscuous : 1; bool mSrcMatchEnabled : 1; @@ -79,9 +99,12 @@ struct Radio Mac::ShortAddress mShortAddress; Mac::ExtAddress mExtAddress; Frame mTxFrame; + otRadioIeInfo mTxIeInfo; otRadioContext mRadioContext; - Array mSrcMatchShortEntries; + Array mSrcMatchShortEntries; Array mSrcMatchExtEntries; + + Array mLinkMetricsEntries; }; } // namespace Nexus