[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.
This commit is contained in:
Jonathan Hui
2026-03-05 01:02:18 -06:00
committed by GitHub
parent 21837d72db
commit 3ac4d4ac19
6 changed files with 154 additions and 10 deletions
+2 -2
View File
@@ -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
+22 -1
View File
@@ -31,6 +31,7 @@
#include <cstdlib>
#include <cstring>
#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));
+2 -1
View File
@@ -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
{
+1
View File
@@ -158,6 +158,7 @@ static void LogVarArgs(Node *aActiveNode, const char *aFormat, va_list aArgs)
vprintf(aFormat, aArgs);
printf("\n");
fflush(stdout);
}
} // namespace Nexus
+100 -2
View File
@@ -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<uint8_t>(static_cast<uint16_t>(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<int16_t>(kRssiOffset))
{
rssi = -static_cast<int16_t>(kRssiOffset);
}
aData[bytes++] =
static_cast<uint8_t>((static_cast<uint16_t>(rssi + kRssiOffset)) * kLinkMetricsMax / kLinkMetricsScale);
}
exit:
return bytes;
}
bool Radio::CanReceiveOnChannel(uint8_t aChannel) const
+27 -4
View File
@@ -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<uint16_t, kMaxSrcMaatchShort> mSrcMatchShortEntries;
Array<uint16_t, kMaxSrcMatchShort> mSrcMatchShortEntries;
Array<Mac::ExtAddress, kMaxSrcMatchExt> mSrcMatchExtEntries;
Array<LinkMetricsInfo, OPENTHREAD_CONFIG_MLE_LINK_METRICS_MAX_SERIES_SUPPORTED> mLinkMetricsEntries;
};
} // namespace Nexus