diff --git a/include/openthread/diag.h b/include/openthread/diag.h index 217abf6cb38..31316c11787 100644 --- a/include/openthread/diag.h +++ b/include/openthread/diag.h @@ -51,6 +51,10 @@ extern "C" { * */ +typedef void (*otDiagOutputCallback)(void *aContext, const char *aFormat, va_list aArguments); + +void otDiagSetOutputCallback(otInstance *aInstance, otDiagOutputCallback aCallback, void *aContext); + /** * Processes a factory diagnostics command line. * diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 2a98ee0ecd7..1a35dcb0859 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -169,6 +169,11 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi #if (OPENTHREAD_FTD || OPENTHREAD_MTD) && OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK otIp6SetReceiveCallback(GetInstancePtr(), &Interpreter::HandleIp6Receive, this); #endif + +#if OPENTHREAD_CONFIG_DIAG_ENABLE + otDiagSetOutputCallback(GetInstancePtr(), &Interpreter::HandleDiagOutput, this); +#endif + ClearAllBytes(mUserCommands); OutputPrompt(); @@ -252,6 +257,13 @@ template <> otError Interpreter::Process(Arg aArgs[]) return error; } + +void Interpreter::HandleDiagOutput(void *aContext, const char *aFormat, va_list aArguments) +{ + static_cast(aContext)->HandleDiagOutput(aFormat, aArguments); +} + +void Interpreter::HandleDiagOutput(const char *aFormat, va_list aArguments) { OutputFormatV(aFormat, aArguments); } #endif template <> otError Interpreter::Process(Arg aArgs[]) diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index b808f7455ff..8ee352b4a0f 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -414,6 +414,11 @@ class Interpreter : public OutputImplementer, public Utils #endif // OPENTHREAD_FTD || OPENTHREAD_MTD +#if OPENTHREAD_CONFIG_DIAG_ENABLE + static void HandleDiagOutput(void *aContext, const char *aFormat, va_list aArguments); + void HandleDiagOutput(const char *aFormat, va_list aArguments); +#endif + void SetCommandTimeout(uint32_t aTimeoutMilli); static void HandleTimer(Timer &aTimer); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8bc006e70b8..7e9e695e755 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -131,6 +131,7 @@ set(COMMON_SOURCES crypto/sha256.cpp crypto/storage.cpp diags/factory_diags.cpp + diags/sitesurvey.cpp instance/instance.cpp mac/channel_mask.cpp mac/data_poll_handler.cpp @@ -285,6 +286,7 @@ set(RADIO_COMMON_SOURCES crypto/crypto_platform.cpp crypto/storage.cpp diags/factory_diags.cpp + diags/sitesurvey.cpp instance/instance.cpp mac/link_raw.cpp mac/mac_frame.cpp diff --git a/src/core/api/diags_api.cpp b/src/core/api/diags_api.cpp index a87bde58135..495efa13819 100644 --- a/src/core/api/diags_api.cpp +++ b/src/core/api/diags_api.cpp @@ -42,6 +42,13 @@ using namespace ot; +void otDiagSetOutputCallback(otInstance *aInstance, otDiagOutputCallback aCallback, void *aContext) +{ + AssertPointerIsNotNull(aCallback); + + AsCoreType(aInstance).Get().SetOutputCallback(aCallback, aContext); +} + otError otDiagProcessCmdLine(otInstance *aInstance, const char *aString, char *aOutput, size_t aOutputMaxLen) { AssertPointerIsNotNull(aString); diff --git a/src/core/diags/factory_diags.cpp b/src/core/diags/factory_diags.cpp index 78845f1aab6..4c91a6e4eec 100644 --- a/src/core/diags/factory_diags.cpp +++ b/src/core/diags/factory_diags.cpp @@ -48,12 +48,8 @@ #include "radio/radio.hpp" #include "utils/parse_cmdline.hpp" -OT_TOOL_WEAK -otError otPlatDiagProcess(otInstance *aInstance, - uint8_t aArgsLength, - char *aArgs[], - char *aOutput, - size_t aOutputMaxLen) +OT_TOOL_WEAK otError +otPlatDiagProcess(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen) { OT_UNUSED_VARIABLE(aArgsLength); OT_UNUSED_VARIABLE(aArgs); @@ -194,6 +190,7 @@ const struct Diags::Command Diags::sCommands[] = { {"radio", &Diags::ProcessRadio}, {"repeat", &Diags::ProcessRepeat}, {"send", &Diags::ProcessSend}, + {"sitesurvey", &Diags::ProcessSiteSurvey}, {"start", &Diags::ProcessStart}, {"stats", &Diags::ProcessStats}, {"stop", &Diags::ProcessStop}, @@ -210,6 +207,7 @@ Diags::Diags(Instance &aInstance) , mTxLen(0) , mRepeatActive(false) , mDiagSendOn(false) + , mSiteSurvey(aInstance, mTxPacket) { mStats.Clear(); } @@ -234,6 +232,7 @@ Error Diags::ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, s mChannel = static_cast(value); IgnoreError(Get().Receive(mChannel)); otPlatDiagChannelSet(mChannel); + mSiteSurvey.SetChannel(mChannel); snprintf(aOutput, aOutputMaxLen, "set channel to %d\r\nstatus 0x%02x\r\n", mChannel, error); } @@ -276,6 +275,7 @@ Error Diags::ProcessRepeat(uint8_t aArgsLength, char *aArgs[], char *aOutput, si Error error = kErrorNone; VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState); + VerifyOrExit(mSiteSurvey.IsRunning(), error = kErrorBusy); VerifyOrExit(aArgsLength > 0, error = kErrorInvalidArgs); if (strcmp(aArgs[0], "stop") == 0) @@ -316,6 +316,7 @@ Error Diags::ProcessSend(uint8_t aArgsLength, char *aArgs[], char *aOutput, size long value; VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState); + // VerifyOrExit(mSiteSurvey.IsRunning(), error = kErrorBusy); VerifyOrExit(aArgsLength == 2, error = kErrorInvalidArgs); SuccessOrExit(error = ParseLong(aArgs[0], value)); @@ -489,11 +490,20 @@ Error Diags::ProcessRadio(uint8_t aArgsLength, char *aArgs[], char *aOutput, siz return error; } +Error Diags::ProcessSiteSurvey(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen) +{ + return mSiteSurvey.Process(aArgsLength, aArgs, aOutput, aOutputMaxLen); +} + extern "C" void otPlatDiagAlarmFired(otInstance *aInstance) { AsCoreType(aInstance).Get().AlarmFired(); } void Diags::AlarmFired(void) { - if (mRepeatActive) + if (mSiteSurvey.IsRunning()) + { + mSiteSurvey.TimerFired(); + } + else if (mRepeatActive) { uint32_t now = otPlatAlarmMilliGetNow(); @@ -508,45 +518,61 @@ void Diags::AlarmFired(void) void Diags::ReceiveDone(otRadioFrame *aFrame, Error aError) { - if (aError == kErrorNone) + if (mSiteSurvey.IsRunning()) { - // for sensitivity test, only record the rssi and lqi for the first and last packet - if (mStats.mReceivedPackets == 0) + VerifyOrExit(aFrame != nullptr); + mSiteSurvey.ReceiveDone(*reinterpret_cast(aFrame), aError); + } + else + { + if (aError == kErrorNone) { - mStats.mFirstRssi = aFrame->mInfo.mRxInfo.mRssi; - mStats.mFirstLqi = aFrame->mInfo.mRxInfo.mLqi; - } + // for sensitivity test, only record the rssi and lqi for the first and last packet + if (mStats.mReceivedPackets == 0) + { + mStats.mFirstRssi = aFrame->mInfo.mRxInfo.mRssi; + mStats.mFirstLqi = aFrame->mInfo.mRxInfo.mLqi; + } - mStats.mLastRssi = aFrame->mInfo.mRxInfo.mRssi; - mStats.mLastLqi = aFrame->mInfo.mRxInfo.mLqi; + mStats.mLastRssi = aFrame->mInfo.mRxInfo.mRssi; + mStats.mLastLqi = aFrame->mInfo.mRxInfo.mLqi; - mStats.mReceivedPackets++; + mStats.mReceivedPackets++; + } } +exit: otPlatDiagRadioReceived(&GetInstance(), aFrame, aError); } void Diags::TransmitDone(Error aError) { - VerifyOrExit(mDiagSendOn); - mDiagSendOn = false; - - if (aError == kErrorNone) + if (mSiteSurvey.IsRunning()) + { + mSiteSurvey.TransmitDone(aError); + } + else { - mStats.mSentPackets++; + VerifyOrExit(mDiagSendOn); + mDiagSendOn = false; - if (mTxPackets > 1) + if (aError == kErrorNone) { - mTxPackets--; - } - else - { - ExitNow(); + mStats.mSentPackets++; + + if (mTxPackets > 1) + { + mTxPackets--; + } + else + { + ExitNow(); + } } - } - VerifyOrExit(!mRepeatActive); - TransmitPacket(); + VerifyOrExit(!mRepeatActive); + TransmitPacket(); + } exit: return; @@ -554,6 +580,16 @@ void Diags::TransmitDone(Error aError) #endif // OPENTHREAD_RADIO +void Diags::SetOutputCallback(otDiagOutputCallback aCallback, void *aContext) +{ + OT_UNUSED_VARIABLE(aCallback); + OT_UNUSED_VARIABLE(aContext); + +#if OPENTHREAD_FTD || OPENTHREAD_MTD || (OPENTHREAD_RADIO && OPENTHREAD_RADIO_CLI) + mSiteSurvey.SetOutputCallback(aCallback, aContext); +#endif +} + Error Diags::ProcessContinuousWave(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen) { Error error = kErrorInvalidArgs; diff --git a/src/core/diags/factory_diags.hpp b/src/core/diags/factory_diags.hpp index 1b72a15639e..d2a946585e6 100644 --- a/src/core/diags/factory_diags.hpp +++ b/src/core/diags/factory_diags.hpp @@ -47,6 +47,8 @@ #include "common/locator.hpp" #include "common/non_copyable.hpp" #include "common/string.hpp" +#include "diags/sitesurvey.hpp" +#include "mac/mac_frame.hpp" namespace ot { namespace FactoryDiags { @@ -123,6 +125,8 @@ class Diags : public InstanceLocator, private NonCopyable */ void TransmitDone(Error aError); + void SetOutputCallback(otDiagOutputCallback aCallback, void *aContext); + private: static constexpr uint8_t kMaxArgs = OPENTHREAD_CONFIG_DIAG_CMD_LINE_ARGS_MAX; @@ -196,6 +200,7 @@ class Diags : public InstanceLocator, private NonCopyable #if OPENTHREAD_RADIO && !OPENTHREAD_RADIO_CLI Error ProcessEcho(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen); #endif + Error ProcessSiteSurvey(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen); Error GetRawPowerSetting(RawPowerSetting &aRawPowerSetting); Error GetPowerSettings(uint8_t aChannel, PowerSettings &aPowerSettings); @@ -219,6 +224,8 @@ class Diags : public InstanceLocator, private NonCopyable uint8_t mTxLen; bool mRepeatActive; bool mDiagSendOn; + + SiteSurvey mSiteSurvey; #endif }; diff --git a/src/core/diags/sitesurvey.cpp b/src/core/diags/sitesurvey.cpp new file mode 100644 index 00000000000..fcc1b28b3c8 --- /dev/null +++ b/src/core/diags/sitesurvey.cpp @@ -0,0 +1,792 @@ +/* + * Copyright (c) 2024, 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 diagnostics module. + */ + +#include "sitesurvey.hpp" + +#if OPENTHREAD_CONFIG_DIAG_ENABLE + +#include +#include + +#include +#include + +#include "common/as_core_type.hpp" +#include "common/code_utils.hpp" +#include "common/locator_getters.hpp" +#include "instance/instance.hpp" +#include "mac/sub_mac.hpp" +#include "radio/radio.hpp" +#include "utils/parse_cmdline.hpp" + +namespace ot { +namespace FactoryDiags { + +using namespace ot::Utils::CmdLineParser; + +RegisterLogModule("SiteSurvey"); + +#define TIMER_START(aDelay) \ + { \ + TimerStart(aDelay); \ + } + +SiteSurvey::SiteSurvey(Instance &aInstance, otRadioFrame *aFrame) + : mInstance(aInstance) + , mTxFrame(*static_cast(aFrame)) + , mDstAddress() + , mConfig() + , mReport() + , mState(kStateDisabled) + , mChannel(12) + , mTxCounter(0) + , mTxSequence(0) + , mIsServer(false) + , mDiagOutputCallback(nullptr) + , mDiagOutputCallbackContext(nullptr) +{ + mInitStamp = otPlatAlarmMilliGetNow(); +} + +Error SiteSurvey::Process(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen) +{ + Error error = kErrorNone; + + VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState); + VerifyOrExit(aArgsLength > 0, error = kErrorInvalidArgs); + + if (strcmp(aArgs[0], "disable") == 0) + { + TimerStop(); + SetRxChannel(mChannel); + SetState(kStateDisabled); + } + else if (strcmp(aArgs[0], "server") == 0) + { + VerifyOrExit(!IsRunning(), error = kErrorInvalidState); + + mIsServer = true; + mConfig.Reset(); + mReport.Reset(); + + SetRxChannel(mChannel); + SetState(kStateServerWaitingRequest); + + snprintf(aOutput, aOutputMaxLen, "Listening channel %u on %s\r\n", mChannel, + mInstance.Get().GetExtAddress().ToString().AsCString()); + } + else if (strcmp(aArgs[0], "client") == 0) + { + bool isDstAddrSet = false; + int8_t i; + + VerifyOrExit(!IsRunning(), error = kErrorInvalidState); + + mReport.Reset(); + mConfig.Reset(); + mConfig.SetChannel(mChannel); + + for (i = 1; i + 1 < aArgsLength; i += 2) + { + if (strcmp(aArgs[i], "dstaddr") == 0) + { + SuccessOrExit(error = ParseAsHexString(aArgs[i + 1], mDstAddress.m8, sizeof(mDstAddress.m8))); + isDstAddrSet = true; + } + else if (strcmp(aArgs[i], "direction") == 0) + { + Config::Direction direction; + + if (strcmp(aArgs[i + 1], "tx") == 0) + { + direction = Config::kDirectionTx; + } + else if (strcmp(aArgs[i + 1], "rx") == 0) + { + direction = Config::kDirectionRx; + } + else + { + ExitNow(error = kErrorInvalidArgs); + } + + mConfig.SetDirection(direction); + } + else if (strcmp(aArgs[i], "attempts") == 0) + { + uint8_t attempts; + + SuccessOrExit(error = ParseAsUint8(aArgs[i + 1], attempts)); + VerifyOrExit(attempts > 0, error = kErrorInvalidArgs); + mConfig.SetMaxAttempts(attempts); + } + else if (strcmp(aArgs[i], "channel") == 0) + { + uint8_t channel; + + SuccessOrExit(error = ParseAsUint8(aArgs[i + 1], channel)); + VerifyOrExit(channel >= Radio::kChannelMin && channel <= Radio::kChannelMax, error = kErrorInvalidArgs); + mConfig.SetChannel(channel); + } + else if (strcmp(aArgs[i], "length") == 0) + { + uint8_t length; + + SuccessOrExit(error = ParseAsUint8(aArgs[i + 1], length)); + VerifyOrExit((length >= kMinDataFrameSize) && (length <= kMaxRadioFrameSize), + error = kErrorInvalidArgs); + mConfig.SetFrameLength(length); + } + else if (strcmp(aArgs[i], "number") == 0) + { + uint16_t number; + + SuccessOrExit(error = ParseAsUint16(aArgs[i + 1], number)); + VerifyOrExit(number > 0, error = kErrorInvalidArgs); + mConfig.SetNumFrames(number); + } + else if (strcmp(aArgs[i], "interval") == 0) + { + uint16_t interval; + + SuccessOrExit(error = ParseAsUint16(aArgs[i + 1], interval)); + VerifyOrExit(interval > 0, error = kErrorInvalidArgs); + mConfig.SetTxInterval(interval); + } + else + { + ExitNow(error = kErrorInvalidArgs); + } + } + + if (!isDstAddrSet) + { + snprintf(aOutput, aOutputMaxLen, "Destination extended address is not set\r\n"); + ExitNow(error = kErrorInvalidArgs); + } + + mTxSequence = 0; + mTxCounter = 0; + mIsServer = false; + mIsReportReceived = false; + + SetRxChannel(mChannel); + + snprintf(aOutput, aOutputMaxLen, "Client connecting to %s, channel %u\r\n", mDstAddress.ToString().AsCString(), + mChannel); + TIMER_START(kHandshakeInterval); + SetState(kStateClientSendingRequest); + } + else + { + error = kErrorInvalidArgs; + } + +exit: + if (error != kErrorNone) + { + snprintf(aOutput, aOutputMaxLen, "failed\r\nstatus %#x\r\n", error); + } + + return error; +} + +bool SiteSurvey::IsValidFrame(const Mac::RxFrame &aFrame, FrameType aType) +{ + bool ret = false; + uint8_t length; + + VerifyOrExit((aFrame.GetType() == Mac::Frame::kTypeData) && !aFrame.GetAckRequest() && aFrame.IsDstAddrPresent() && + !aFrame.IsSrcPanIdPresent() && !aFrame.IsDstPanIdPresent()); + VerifyOrExit(aFrame.GetPayloadLength() >= 1); + VerifyOrExit((aFrame.GetPayload()[0] & kFrameTypeMask) == aType); + + switch (aType) + { + case kFrameTypeRequest: + length = sizeof(Config); + VerifyOrExit(aFrame.IsSrcAddrPresent()); + break; + case kFrameTypeAck: + length = sizeof(kFrameTypeAck); + break; + case kFrameTypeReport: + length = Report::GetSize(); + break; + case kFrameTypeData: + length = aFrame.GetPayloadLength(); + break; + default: + ExitNow(); + break; + } + + VerifyOrExit(aFrame.GetPayloadLength() == length); + + ret = true; + +exit: + return ret; +} + +void SiteSurvey::BuildFrame(uint8_t aChannel, + uint8_t aSequence, + bool aIsSrcAddrPresent, + const uint8_t *aPayload, + uint8_t aLength) +{ + Mac::Addresses addrs; + Mac::PanIds panIds; + + if (aIsSrcAddrPresent) + { + addrs.mSource.SetExtended(mInstance.Get().GetExtAddress()); + } + + addrs.mDestination.SetExtended(mDstAddress); + + mTxFrame.InitMacHeader(Mac::Frame::kTypeData, Mac::Frame::kVersion2015, addrs, panIds, Mac::Frame::kSecurityNone); + mTxFrame.SetChannel(aChannel); + mTxFrame.SetSequence(aSequence); + mTxFrame.SetAckRequest(false /* aAckRequest */); + mTxFrame.SetCsmaCaEnabled(true /* aCsmaCaEnabled */); + mTxFrame.SetMaxCsmaBackoffs(kMaxCsmaBackoffsDirect); + + if ((aPayload != nullptr) && (aLength != 0)) + { + memcpy(mTxFrame.GetPayload(), aPayload, aLength); + mTxFrame.SetPayloadLength(aLength); + } + else + { + mTxFrame.SetPayloadLength(0); + } +} + +void SiteSurvey::SendRequestFrame(void) +{ + BuildFrame(mChannel, mTxSequence++, true /* aIsSrcAddrPresent */, reinterpret_cast(&mConfig), + static_cast(sizeof(mConfig))); + IgnoreError(TransmitFrame(mTxFrame)); + mTxCounter++; +} + +void SiteSurvey::SendAckFrame(uint8_t aSequence) +{ + uint8_t payload = kFrameTypeAck; + + BuildFrame(mChannel, aSequence, false /* aIsSrcAddrPresent */, &payload, sizeof(payload)); + IgnoreError(TransmitFrame(mTxFrame)); +} + +void SiteSurvey::SendReportFrame(void) +{ + BuildFrame(mChannel, mTxSequence++, false /* aIsSrcAddrPresent */, reinterpret_cast(&mReport), + Report::GetSize()); + IgnoreError(TransmitFrame(mTxFrame)); + mTxCounter++; +} + +void SiteSurvey::SendDataFrame(void) +{ + uint8_t frameLength = mConfig.GetFrameLength(); + uint8_t payload[kMaxRadioFrameSize]; + uint8_t payloadLength; + + OT_ASSERT(frameLength >= kMinDataFrameSize); + + payloadLength = frameLength - (kMinDataFrameSize - sizeof(kFrameTypeData)); + payload[0] = kFrameTypeData; + + for (uint8_t i = 1; i < payloadLength; i++) + { + payload[i] = i; + } + + BuildFrame(mConfig.GetChannel(), mTxSequence++, false /* aIsSrcAddrPresent */, payload, payloadLength); + mTxFrame.SetCsmaCaEnabled(false /* aCsmaCaEnabled */); + + IgnoreError(TransmitFrame(mTxFrame)); + mTxCounter++; +} + +otError SiteSurvey::TransmitFrame(Mac::TxFrame &aFrame) +{ + otError error; + + mTxTimeStamp = otPlatAlarmMilliGetNow(); + SuccessOrExit(error = mInstance.Get().Transmit(aFrame)); + LogFrame(&aFrame, true /* aIsTxFrame */); + +exit: + return error; +} + +otError SiteSurvey::SetRxChannel(uint8_t aChannel) +{ + otPlatDiagChannelSet(aChannel); + return mInstance.Get().Receive(aChannel); +} + +void SiteSurvey::SetState(State aState) +{ + LogDebg("State: %s -> %s", StateToString(mState), StateToString(aState)); + mState = aState; +} + +void SiteSurvey::OutputReport(void) +{ + uint32_t lossRate; + + lossRate = (mConfig.GetNumFrames() - mReport.mNumReceivedFrames) * 1000 / mConfig.GetNumFrames(); + + OutputLine("Report: Direction:%s, Ch:%u, Len:%u, Sent:%u, Received:%u, LossRate:%d.%d%%, MinRssi:%d, AvgRssi:%d, " + "MaxRssi:%d, MinLqi:%u, " + "AvgLqi:%u, MaxLqi:%u", + mConfig.GetDirection() == Config::kDirectionTx ? "tx" : "rx", mConfig.GetChannel(), + mConfig.GetFrameLength(), mConfig.GetNumFrames(), mReport.mNumReceivedFrames, lossRate / 10, + lossRate % 10, mReport.mMinRssi, mReport.mAvgRssi, mReport.mMaxRssi, mReport.mMinLqi, mReport.mAvgLqi, + mReport.mMaxLqi); +} + +Error SiteSurvey::TimerFired(void) +{ + uint16_t delay; + + switch (mState) + { + /*----------------client-----------------*/ + case kStateClientSendingRequest: + if (mTxCounter < mConfig.GetMaxAttempts()) + { + SendRequestFrame(); + TIMER_START(kHandshakeInterval); + } + else + { + // Timeout, No response recevied from peer. Stop testing + SetState(kStateDisabled); + OutputLine("Failed to connect with %s", mDstAddress.ToString().AsCString()); + } + break; + + case kStateClientWaitingReport: + // Reports have been sent out. + SetState(kStateDisabled); + + if (mIsReportReceived) + { + OutputReport(); + OutputLine("Disconnected from %s", mDstAddress.ToString().AsCString()); + } + else + { + OutputLine("Disconnected from %s, timeout", mDstAddress.ToString().AsCString()); + } + break; + + /*----------------server-----------------*/ + case kStateServerWaitingAck: + if (mTxCounter < mConfig.GetMaxAttempts()) + { + SendAckFrame(mTxSequence++); + mTxCounter++; + TIMER_START(kHandshakeInterval); + } + else + { + // Failed to receive ACK from client, timeout + SetState(kStateServerWaitingRequest); + OutputLine("Disconnected from %s, timeout", mDstAddress.ToString().AsCString()); + } + break; + + case kStateServerSendingReport: + if (mTxCounter < mConfig.GetMaxAttempts()) + { + SendReportFrame(); + TIMER_START(kReportInterval); + } + else + { + SetState(kStateServerWaitingRequest); + OutputLine("Disconnected from %s, timeout", mDstAddress.ToString().AsCString()); + } + break; + + /*----------------common-----------------*/ + case kStateConnectionEstablished: + if (mIsServer) + { + if (mConfig.GetDirection() == Config::kDirectionTx) + { + mReport.Reset(); + + delay = mConfig.GetNumFrames() * mConfig.GetTxInterval() + kRxGuardTime; + TIMER_START(delay); + SetState(kStateReceivingData); + } + else + { + mTxCounter = 0; + mTxSequence = 0; + SetState(kStateSendingData); + TIMER_START(kRxGuardTime); + } + } + else + { + if (mConfig.GetDirection() == Config::kDirectionTx) + { + mTxCounter = 0; + mTxSequence = 0; + + // Delay to gurantee that the receiver has switched to receive mode. + TIMER_START(kRxGuardTime); + SetState(kStateSendingData); + } + else + { + delay = mConfig.GetNumFrames() * mConfig.GetTxInterval() + kRxGuardTime; + TIMER_START(delay); + SetState(kStateReceivingData); + } + } + SetRxChannel(mConfig.GetChannel()); + break; + + case kStateSendingData: + if (mTxCounter < mConfig.GetNumFrames()) + { + SendDataFrame(); + OutputLine("TX, Seq=%u, Ch=%u, Len=%u", mTxFrame.GetSequence(), mConfig.GetChannel(), mTxFrame.GetLength()); + TIMER_START(mConfig.GetTxInterval()); + } + else + { + if (mIsServer) + { + TimerStop(); + SetState(kStateServerWaitingRequest); + OutputLine("Disconnected from %s", mDstAddress.ToString().AsCString()); + } + else + { + uint32_t timeout; + + // Set timeout time that waiting for the report. + timeout = (mConfig.GetMaxAttempts() + 1) * kReportInterval; + TIMER_START(timeout); + SetState(kStateClientWaitingReport); + } + + SetRxChannel(mChannel); + } + break; + + case kStateReceivingData: + // All frames should have been sent out + mReport.UpdateAvgRssiLqi(); + + if (mIsServer) + { + mTxSequence = 0; + mTxCounter = 0; + TIMER_START(kStateServerSendingReport); + SetState(kStateServerSendingReport); + } + else + { + SetState(kStateDisabled); + OutputReport(); + OutputLine("Disconnected from %s", mDstAddress.ToString().AsCString()); + } + SetRxChannel(mChannel); + break; + + default: + break; + } + + return kErrorNone; +} + +void SiteSurvey::ReceiveDone(const Mac::RxFrame &aFrame, Error aError) +{ + uint32_t delay; + uint32_t timeout; + Mac::Address dstAddr; + + VerifyOrExit(aError == kErrorNone); + VerifyOrExit((aFrame.GetPayloadLength() >= 1) && aFrame.IsDstAddrPresent()); + SuccessOrExit(aFrame.GetDstAddr(dstAddr)); + VerifyOrExit(dstAddr.IsExtended()); + + LogFrame(&aFrame, false /* aIsTxFrame */); + + switch (mState) + { + /*-----------------client-----------------------------*/ + case kStateClientSendingRequest: + { + uint32_t txTimeStamp = mTxTimeStamp; + uint32_t delta; + + VerifyOrExit(IsValidFrame(aFrame, kFrameTypeAck)); + VerifyOrExit((aFrame.GetSequence() + 1) == mTxSequence); + + TimerStop(); + SendAckFrame(aFrame.GetSequence()); + + delta = otPlatAlarmMilliGetNow() - txTimeStamp; + delay = (mConfig.GetMaxAttempts() - mTxCounter) * kHandshakeInterval - delta; + TIMER_START(delay); + SetState(kStateConnectionEstablished); + + OutputLine("Connected with %s", mDstAddress.ToString().AsCString()); + } + break; + + case kStateClientWaitingReport: + { + VerifyOrExit(IsValidFrame(aFrame, kFrameTypeReport)); + + if (!mIsReportReceived) + { + memcpy(&mReport, aFrame.GetPayload(), sizeof(Report)); + + mIsReportReceived = true; + } + + SendAckFrame(aFrame.GetSequence()); + } + break; + + /*-----------------server-----------------------------*/ + case kStateServerWaitingRequest: + { + Mac::Address srcAddr; + + VerifyOrExit(IsValidFrame(aFrame, kFrameTypeRequest)); + SuccessOrExit(aFrame.GetSrcAddr(srcAddr)); + VerifyOrExit(srcAddr.IsExtended()); + memcpy(&mConfig, aFrame.GetPayload(), sizeof(Config)); + VerifyOrExit(mConfig.GetMaxAttempts() > aFrame.GetSequence()); + + mDstAddress = srcAddr.GetExtended(); + + mTxSequence = aFrame.GetSequence(); + SendAckFrame(mTxSequence++); + mTxCounter = mTxSequence; + + TIMER_START(kHandshakeInterval); + SetState(kStateServerWaitingAck); + } + break; + + case kStateServerWaitingAck: + if (IsValidFrame(aFrame, kFrameTypeAck)) + { + uint32_t txTimeStamp = mTxTimeStamp; + uint32_t delta; + VerifyOrExit(aFrame.GetSequence() + 1 == mTxSequence); + + delta = otPlatAlarmMilliGetNow() - txTimeStamp; + timeout = (mConfig.GetMaxAttempts() - aFrame.GetSequence()) * kHandshakeInterval - delta; + TIMER_START(timeout); + SetState(kStateConnectionEstablished); + + OutputLine("Connected with %s", mDstAddress.ToString().AsCString()); + } + else if (IsValidFrame(aFrame, kFrameTypeRequest)) + { + mTxSequence = aFrame.GetSequence(); + + // The client failed to receive the previous sent ACK. + SendAckFrame(mTxSequence++); + mTxCounter = mTxSequence; + } + break; + + case kStateServerSendingReport: + VerifyOrExit(IsValidFrame(aFrame, kFrameTypeAck)); + VerifyOrExit(aFrame.GetSequence() + 1 == mTxSequence); + TimerStop(); + SetState(kStateServerWaitingRequest); + OutputReport(); + OutputLine("Disconnected from %s", mDstAddress.ToString().AsCString()); + break; + + /*-----------------common-----------------------------*/ + case kStateConnectionEstablished: + VerifyOrExit(!mIsServer); + VerifyOrExit(IsValidFrame(aFrame, kFrameTypeAck)); + SendAckFrame(aFrame.GetSequence()); + break; + + case kStateSendingData: + // Do nothing + break; + + case kStateReceivingData: + VerifyOrExit(IsReceiver()); + VerifyOrExit(IsValidFrame(aFrame, kFrameTypeData)); + + mReport.UpdateRssi(aFrame.GetRssi()); + mReport.UpdateLqi(aFrame.GetLqi()); + mReport.SetNumReceivedFrames(mReport.GetNumReceivedFrames() + 1); + + OutputLine("RX, Seq=%u, Ch=%u, Len=%u, Rssi=%d, Lqi=%u", aFrame.GetSequence(), mConfig.GetChannel(), + aFrame.GetLength(), aFrame.GetRssi(), aFrame.GetLqi()); + + timeout = (mConfig.GetNumFrames() - aFrame.GetSequence()) * mConfig.GetTxInterval() + kRxGuardTime; + TIMER_START(timeout); + break; + + default: + break; + } + +exit: + return; +} + +bool SiteSurvey::IsReceiver(void) +{ + return (mIsServer && (mConfig.GetDirection() == Config::kDirectionTx)) || + ((!mIsServer) && (mConfig.GetDirection() == Config::kDirectionRx)); +} + +void SiteSurvey::SetOutputCallback(otDiagOutputCallback aCallback, void *aContext) +{ + mDiagOutputCallback = aCallback; + mDiagOutputCallbackContext = aContext; +} + +const char *SiteSurvey::FrameTypeToString(FrameType aFrameType) +{ + static const char *const kFrameTypeStrings[] = { + "Request", // (0) kFrameTypeRequest + "Ack", // (1) kFrameTypeAck + "Report", // (2) kFrameTypeReport + "Data" // (3) kFrameTypeData + }; + + static_assert(kFrameTypeRequest == 0, "kFrameTypeRequest value is incorrect"); + static_assert(kFrameTypeAck == 1, "kFrameTypeAck value is incorrect"); + static_assert(kFrameTypeReport == 2, "kFrameTypeReport value is incorrect"); + static_assert(kFrameTypeData == 3, "kFrameTypeData value is incorrect"); + + return kFrameTypeStrings[aFrameType]; +} + +const char *SiteSurvey::StateToString(State aState) +{ + static const char *const kStateStrings[] = { + "Disabled", // (0) kStateDisabled + "ClientSendingRequest", // (1) kStateClientSendingRequest + "ServerWaitingRequest", // (2) kStateServerWaitingRequest + "ServerWaitingAck", // (3) kStateServerWaitingAck + "ConnectionEstablished", // (4) kStateConnectionEstablished + "SendingData", // (5) kStateSendingData + "ReceivingData", // (6) kStateReceivingData + "ServerSendingReport", // (7) kStateServerSendingReport + "ClientWaitingReport", // (8) kStateClientWaitingReport + }; + + static_assert(kStateDisabled == 0, "kStateDisabled value is incorrect"); + static_assert(kStateClientSendingRequest == 1, "kStateClientSendingRequest value is incorrect"); + static_assert(kStateServerWaitingRequest == 2, "kStateServerWaitingRequest value is incorrect"); + static_assert(kStateServerWaitingAck == 3, "kStateServerWaitingAck value is incorrect"); + static_assert(kStateConnectionEstablished == 4, "kStateConnectionEstablished value is incorrect"); + static_assert(kStateSendingData == 5, "kStateSendingData value is incorrect"); + static_assert(kStateReceivingData == 6, "kStateReceivingData value is incorrect"); + static_assert(kStateServerSendingReport == 7, "kStateServerSendingReport value is incorrect"); + static_assert(kStateClientWaitingReport == 8, "kStateClientWaitingReport value is incorrect"); + + return kStateStrings[aState]; +} + +void SiteSurvey::TimerStart(uint32_t aDelay) { otPlatAlarmMilliStartAt(&mInstance, otPlatAlarmMilliGetNow(), aDelay); } +void SiteSurvey::TimerStop(void) { otPlatAlarmMilliStop(&mInstance); } + +void SiteSurvey::LogFrame(const Mac::Frame *aFrame, bool aIsTxFrame) +{ +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_DEBG) + FrameType type; + + VerifyOrExit((aFrame->GetPayloadLength() >= 1) && aFrame->IsDstAddrPresent()); + type = static_cast(aFrame->GetPayload()[0] & kFrameTypeMask); + LogDebg("[Seq=%u] %s %s", aFrame->GetSequence(), aIsTxFrame ? "Sent" : "Received", FrameTypeToString(type)); + +exit: + return; +#else + OT_UNUSED_VARIABLE(aFrame); + OT_UNUSED_VARIABLE(aIsTxFrame); +#endif +} + +void SiteSurvey::Output(const char *aFormat, ...) +{ + va_list args; + + va_start(args, aFormat); + if (mDiagOutputCallback != nullptr) + { + mDiagOutputCallback(mDiagOutputCallbackContext, aFormat, args); + } + va_end(args); +} + +void SiteSurvey::OutputLine(const char *aFormat, ...) +{ + va_list args; + + va_start(args, aFormat); + if (mDiagOutputCallback != nullptr) + { + // uint32_t time = (otPlatAlarmMilliGetNow() - mInitStamp); + + // Output("%4u.%03u: ", time / 1000, time % 1000); + mDiagOutputCallback(mDiagOutputCallbackContext, aFormat, args); + } + va_end(args); + + Output("\r\n"); +} + +void SiteSurvey::TransmitDone(Error aError) { OT_UNUSED_VARIABLE(aError); } +} // namespace FactoryDiags +} // namespace ot +#endif // OPENTHREAD_CONFIG_DIAG_ENABLE diff --git a/src/core/diags/sitesurvey.hpp b/src/core/diags/sitesurvey.hpp new file mode 100644 index 00000000000..31550e59db9 --- /dev/null +++ b/src/core/diags/sitesurvey.hpp @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2024, 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 contains definitions for the diagnostics module. + */ + +#ifndef SITE_SURVEY_HPP_ +#define SITE_SURVEY_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_DIAG_ENABLE + +#include + +#include +#include + +#include "common/clearable.hpp" +#include "common/encoding.hpp" +#include "common/error.hpp" +#include "common/locator.hpp" +#include "common/new.hpp" +#include "common/non_copyable.hpp" +#include "common/string.hpp" +#include "mac/mac_frame.hpp" +#include "mac/mac_types.hpp" + +namespace ot { +namespace FactoryDiags { +/* +client Server + +SendingRequest Request(SEQ=0) ListeningRequest + ----------------------------------------> + ACK(SEQ=0) + <---------------------------------------- +ConEstablished ACK(SEQ=0) RequestReceived + ----------------------------------------> + ConEstablished + +SendingData + Data ReceivingData + ========================================> + +WaitingReport SendingReport + Report(SEQ=0) + <---------------------------------------- + ACK(SEQ=0) + ----------------------------------------> +Disabled ListeningRequest + + +*/ + +class SiteSurvey : private NonCopyable +{ +public: + SiteSurvey(Instance &aInstance, otRadioFrame *aFrame); + + Error Process(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen); + + void SetOutputCallback(otDiagOutputCallback aCallback, void *aContext); + + bool IsRunning(void) { return (mState != kStateDisabled); } + + Error TimerFired(void); + + void ReceiveDone(const Mac::RxFrame &aFrame, Error aError); + + void TransmitDone(Error aError); + + void SetChannel(uint8_t aChannel) { mChannel = aChannel; } + +private: + static constexpr uint8_t kMaxCsmaBackoffsDirect = OPENTHREAD_CONFIG_MAC_MAX_CSMA_BACKOFFS_DIRECT; + static constexpr uint8_t kMaxRadioFrameSize = OT_RADIO_FRAME_MAX_SIZE; + + enum FrameType : uint8_t + { + kFrameTypeRequest = 0, + kFrameTypeAck = 1, + kFrameTypeReport = 2, + kFrameTypeData = 3, + kFrameTypeMask = 0x03, + }; + + enum State : uint8_t + { + kStateDisabled, + kStateClientSendingRequest, + kStateServerWaitingRequest, + kStateServerWaitingAck, + kStateConnectionEstablished, + kStateSendingData, + kStateReceivingData, + kStateServerSendingReport, + kStateClientWaitingReport, + }; + + enum + { + kHandshakeInterval = 40, + kReportInterval = 40, + kRxGuardTime = 40, + kMinDataFrameSize = 14, ///< FCF(2) + Seq(1) + DstExtendedAddr(8) + FrameType(1) + FCS(2) + }; + + OT_TOOL_PACKED_BEGIN + class Config + { + public: + enum Direction : uint8_t + { + kDirectionTx, ///< Sends data frame from client to server. + kDirectionRx, ///< Sends data frame from server to client. + }; + + Config() + : mType(kFrameTypeRequest) + , mDirection(kDirectionTx) + , mChannel(kDefaultChannel) + , mMaxAttempts(kDefaultMaxAttempts) + , mFrameLength(kDefaultFrameLength) + { + SetNumFrames(kDefaultNumFrames); + SetTxInterval(kDefaultTxTnterval); + } + + void Reset(void) { new (this) Config(); } + void SetChannel(uint8_t aChannel) { mChannel = aChannel; } + void SetDirection(Direction aDirection) { mDirection = aDirection; } + void SetMaxAttempts(uint8_t aMaxAttempts) { mMaxAttempts = aMaxAttempts; } + void SetFrameLength(uint8_t aFrameLength) { mFrameLength = aFrameLength; } + void SetNumFrames(uint16_t aNumFrames) + { + LittleEndian::WriteUint16(aNumFrames, reinterpret_cast(&mNumFrames)); + } + + void SetTxInterval(uint16_t aInterval) + { + LittleEndian::WriteUint16(aInterval, reinterpret_cast(&mTxInterval)); + } + + uint8_t GetFrameType(void) { return mType; } + uint8_t GetChannel(void) { return mChannel; } + Direction GetDirection(void) { return static_cast(mDirection); } + uint8_t GetMaxAttempts(void) { return mMaxAttempts; } + uint8_t GetFrameLength(void) { return mFrameLength; } + uint16_t GetNumFrames(void) { return LittleEndian::ReadUint16(reinterpret_cast(&mNumFrames)); } + uint16_t GetTxInterval(void) { return LittleEndian::ReadUint16(reinterpret_cast(&mTxInterval)); } + + private: + enum + { + kDefaultMaxAttempts = 24, + kDefaultFrameLength = 64, + kDefaultNumFrames = 100, + kDefaultTxTnterval = 20, + kDefaultChannel = 19, + }; + + uint8_t mType : 2; + uint8_t mDirection : 1; + uint8_t mChannel; + uint8_t mMaxAttempts; + uint8_t mFrameLength; + uint16_t mNumFrames; + uint16_t mTxInterval; + } OT_TOOL_PACKED_END; + + OT_TOOL_PACKED_BEGIN + class Report + { + friend class SiteSurvey; + + public: + Report() + : mType(kFrameTypeReport) + , mNumReceivedFrames(0) + , mMinRssi(kMaxRssi) + , mAvgRssi(kMinRssi) + , mMaxRssi(kMinRssi) + , mMinLqi(kMaxLqi) + , mAvgLqi(kMinLqi) + , mMaxLqi(kMinLqi) + , mSumRssi(0) + , mSumLqi(0) + { + } + + void Reset(void) { new (this) Report(); } + void SetNumReceivedFrames(uint16_t aNumReceivedFrames) + { + LittleEndian::WriteUint16(aNumReceivedFrames, reinterpret_cast(&mNumReceivedFrames)); + } + uint16_t GetNumReceivedFrames(void) + { + return LittleEndian::ReadUint16(reinterpret_cast(&mNumReceivedFrames)); + } + + void UpdateRssi(int8_t aRssi) + { + mMaxRssi = (aRssi > mMaxRssi) ? aRssi : mMaxRssi; + mMinRssi = (aRssi < mMinRssi) ? aRssi : mMinRssi; + mSumRssi += aRssi; + } + + void UpdateLqi(uint8_t aLqi) + { + mMaxLqi = (aLqi > mMaxLqi) ? aLqi : mMaxLqi; + mMinLqi = (aLqi < mMinLqi) ? aLqi : mMinLqi; + mSumLqi += aLqi; + } + + void UpdateAvgRssiLqi(void) + { + uint16_t num = GetNumReceivedFrames(); + + if (num != 0) + { + mAvgRssi = mSumRssi / num; + mAvgLqi = mSumLqi / num; + } + } + + static uint8_t GetSize(void) { return sizeof(Report) - sizeof(mSumRssi) - sizeof(mSumLqi); } + + private: + enum + { + kMinRssi = -127, + kMaxRssi = 127, + kMinLqi = 0, + kMaxLqi = 255, + }; + + uint8_t mType : 2; + uint16_t mNumReceivedFrames; + int8_t mMinRssi; + int8_t mAvgRssi; + int8_t mMaxRssi; + uint8_t mMinLqi; + uint8_t mAvgLqi; + uint8_t mMaxLqi; + + int64_t mSumRssi; + uint64_t mSumLqi; + } OT_TOOL_PACKED_END; + + bool IsValidFrame(const Mac::RxFrame &aFrame, FrameType aType); + void BuildFrame(uint8_t aChannel, + uint8_t aSequence, + bool aIsSrcAddrPresent, + const uint8_t *aPayload, + uint8_t aLength); + + void SendRequestFrame(void); + void SendAckFrame(uint8_t aSequence); + void SendReportFrame(void); + void SendDataFrame(void); + otError TransmitFrame(Mac::TxFrame &aFrame); + otError SetRxChannel(uint8_t aChannel); + void SetState(State aState); + void OutputReport(void); + + bool IsReceiver(void); + static const char *FrameTypeToString(FrameType aFrameType); + static const char *StateToString(State aState); + void TimerStart(uint32_t aDelay); + void TimerStop(void); + void LogFrame(const Mac::Frame *aFrame, bool aIsTxFrame); + void Output(const char *aFormat, ...); + void OutputLine(const char *aFormat, ...); + + Instance &mInstance; + Mac::TxFrame &mTxFrame; + Mac::ExtAddress mDstAddress; + Config mConfig; + Report mReport; + State mState; + uint8_t mChannel; + uint16_t mTxCounter; + uint8_t mTxSequence; + bool mIsServer; + bool mIsReportReceived; + + otDiagOutputCallback mDiagOutputCallback; + void *mDiagOutputCallbackContext; + uint32_t mTxTimeStamp; + uint32_t mInitStamp; +}; +} // namespace FactoryDiags +} // namespace ot + +#endif // #if OPENTHREAD_CONFIG_DIAG_ENABLE + +#endif // SITE_SURVEY_HPP_