diff --git a/examples/apps/cli/cli_uart.cpp b/examples/apps/cli/cli_uart.cpp index 38fb838b7..a2b39f43c 100644 --- a/examples/apps/cli/cli_uart.cpp +++ b/examples/apps/cli/cli_uart.cpp @@ -134,9 +134,9 @@ static otError ProcessCommand(void); static void ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength) { - static const char sEraseString[] = {'\b', ' ', '\b'}; - static const char CRNL[] = {'\r', '\n'}; - static const char sCommandPrompt[] = {'>', ' '}; + static const char sEraseString[] = {'\b', ' ', '\b'}; + static const char CRNL[] = {'\r', '\n'}; + static uint8_t sLastChar = '\0'; const uint8_t * end; end = aBuf + aBufLength; @@ -145,17 +145,18 @@ static void ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength) { switch (*aBuf) { - case '\r': case '\n': - Output(CRNL, sizeof(CRNL)); - if (sRxLength > 0) + if (sLastChar == '\r') { - sRxBuffer[sRxLength] = '\0'; - IgnoreError(ProcessCommand()); + break; } - Output(sCommandPrompt, sizeof(sCommandPrompt)); + OT_FALL_THROUGH; + case '\r': + Output(CRNL, sizeof(CRNL)); + sRxBuffer[sRxLength] = '\0'; + IgnoreError(ProcessCommand()); break; #if OPENTHREAD_POSIX && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) @@ -187,6 +188,8 @@ static void ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength) break; } + + sLastChar = *aBuf; } } @@ -194,16 +197,12 @@ static otError ProcessCommand(void) { otError error = OT_ERROR_NONE; - while (sRxBuffer[sRxLength - 1] == '\n' || sRxBuffer[sRxLength - 1] == '\r') + while (sRxLength > 0 && (sRxBuffer[sRxLength - 1] == '\n' || sRxBuffer[sRxLength - 1] == '\r')) { sRxBuffer[--sRxLength] = '\0'; } - if (sRxLength > 0) - { - otCliInputLine(sRxBuffer); - } - + otCliInputLine(sRxBuffer); sRxLength = 0; return error; diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index ed5566c79..3a545ad76 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -104,6 +104,7 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi , mOutputContext(aContext) , mUserCommands(nullptr) , mUserCommandsLength(0) + , mCommandIsPending(false) #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE , mSntpQueryingInProgress(false) #endif @@ -113,6 +114,7 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi #if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE , mTcp(*this) #endif + , mTimer(*aInstance, HandleTimer, this) #if OPENTHREAD_CONFIG_COAP_API_ENABLE , mCoap(*this) #endif @@ -142,22 +144,31 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi #if OPENTHREAD_FTD otThreadSetDiscoveryRequestCallback(mInstance, &Interpreter::HandleDiscoveryRequest, this); #endif + + OutputPrompt(); } void Interpreter::OutputResult(otError aError) { - switch (aError) + OT_ASSERT(mCommandIsPending); + + VerifyOrExit(aError != OT_ERROR_PENDING); + + if (aError == OT_ERROR_NONE) { - case OT_ERROR_NONE: OutputLine("Done"); - break; - - case OT_ERROR_PENDING: - break; - - default: + } + else + { OutputLine("Error %d: %s", aError, otThreadErrorToString(aError)); } + + mCommandIsPending = false; + mTimer.Stop(); + OutputPrompt(); + +exit: + return; } void Interpreter::OutputBytes(const uint8_t *aBytes, uint16_t aLength) @@ -3186,19 +3197,29 @@ void Interpreter::HandlePingStatistics(const otPingSenderStatistics *aStatistics } OutputLine(""); - OutputResult(OT_ERROR_NONE); + + if (!mPingIsAsync) + { + OutputResult(OT_ERROR_NONE); + } } otError Interpreter::ProcessPing(Arg aArgs[]) { otError error = OT_ERROR_NONE; otPingSenderConfig config; + bool async = false; if (aArgs[0] == "stop") { otPingSenderStop(mInstance); ExitNow(); } + else if (aArgs[0] == "async") + { + async = true; + aArgs++; + } memset(&config, 0, sizeof(config)); @@ -3265,7 +3286,14 @@ otError Interpreter::ProcessPing(Arg aArgs[]) config.mStatisticsCallback = Interpreter::HandlePingStatistics; config.mCallbackContext = this; - error = otPingSenderPing(mInstance, &config); + SuccessOrExit(error = otPingSenderPing(mInstance, &config)); + + mPingIsAsync = async; + + if (!async) + { + error = kErrorPending; + } exit: return error; @@ -4669,21 +4697,18 @@ void Interpreter::ProcessLine(char *aBuf) OT_ASSERT(aBuf != nullptr); + // Ignore the command if another command is pending. + VerifyOrExit(!mCommandIsPending, args[0].Clear()); + mCommandIsPending = true; + VerifyOrExit(StringLength(aBuf, kMaxLineLength) <= kMaxLineLength - 1, error = OT_ERROR_PARSE); #if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE otLogNoteCli("Input: %s", aBuf); #endif - error = Utils::CmdLineParser::ParseCmd(aBuf, args); - - if (error != OT_ERROR_NONE) - { - OutputLine("Error: too many args (max %d)", kMaxArgs); - ExitNow(); - } - - VerifyOrExit(!args[0].IsEmpty()); + SuccessOrExit(error = Utils::CmdLineParser::ParseCmd(aBuf, args, kMaxArgs)); + VerifyOrExit(!args[0].IsEmpty(), mCommandIsPending = false); #if OPENTHREAD_CONFIG_DIAG_ENABLE if (otDiagIsEnabled(mInstance) && (args[0] != "diag")) @@ -4709,6 +4734,10 @@ exit: { OutputResult(error); } + else if (!mCommandIsPending) + { + OutputPrompt(); + } } otError Interpreter::ProcessUserCommands(Arg aArgs[]) @@ -4766,6 +4795,7 @@ otError Interpreter::ProcessNetworkDiagnostic(Arg aArgs[]) { SuccessOrExit(error = otThreadSendDiagnosticGet(mInstance, &address, tlvTypes, count, &Interpreter::HandleDiagnosticGetResponse, this)); + SetCommandTimeout(kNetworkDiagnosticTimeoutMsecs); error = OT_ERROR_PENDING; } else if (aArgs[0] == "reset") @@ -4897,13 +4927,8 @@ void Interpreter::HandleDiagnosticGetResponse(otError aError, } } - if (aError == OT_ERROR_NOT_FOUND) - { - aError = OT_ERROR_NONE; - } - exit: - OutputResult(aError); + return; } void Interpreter::OutputMode(uint8_t aIndentSize, const otLinkModeConfig &aMode) @@ -5152,6 +5177,29 @@ void Interpreter::Initialize(otInstance *aInstance, otCliOutputCallback aCallbac Interpreter::sInterpreter = new (&sInterpreterRaw) Interpreter(instance, aCallback, aContext); } +void Interpreter::OutputPrompt(void) +{ + static const char sPrompt[] = "> "; + + OutputFormat("%s", sPrompt); +} + +void Interpreter::HandleTimer(Timer &aTimer) +{ + static_cast(static_cast(aTimer).GetContext())->HandleTimer(); +} + +void Interpreter::HandleTimer(void) +{ + OutputResult(kErrorNone); +} + +void Interpreter::SetCommandTimeout(uint32_t aTimeoutMilli) +{ + OT_ASSERT(mCommandIsPending); + mTimer.Start(aTimeoutMilli); +} + extern "C" void otCliInit(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext) { Interpreter::Initialize(aInstance, aCallback, aContext); diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 416e6a4c0..c8000bc04 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -336,6 +336,8 @@ private: kMaxLineLength = OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH, }; + static constexpr uint32_t kNetworkDiagnosticTimeoutMsecs = 5000; + struct Command { const char *mName; @@ -432,6 +434,7 @@ private: OutputTableSeperator(kTableNumColumns, aWidths); } + void OutputPrompt(void); #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE otError ParsePingInterval(const Arg &aArg, uint32_t &aInterval); #endif @@ -772,6 +775,10 @@ private: bool IsLogging(void) const { return mIsLogging; } void SetIsLogging(bool aIsLogging) { mIsLogging = aIsLogging; } #endif + void SetCommandTimeout(uint32_t aTimeoutMilli); + + static void HandleTimer(Timer &aTimer); + void HandleTimer(void); static constexpr Command sCommands[] = { #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE @@ -969,6 +976,7 @@ private: const otCliCommand *mUserCommands; uint8_t mUserCommandsLength; void * mUserCommandsContext; + bool mCommandIsPending; #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE bool mSntpQueryingInProgress; #endif @@ -981,6 +989,8 @@ private: TcpExample mTcp; #endif + TimerMilliContext mTimer; + #if OPENTHREAD_CONFIG_COAP_API_ENABLE Coap mCoap; #endif @@ -1014,6 +1024,9 @@ private: uint16_t mOutputLength; bool mIsLogging; #endif +#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE + bool mPingIsAsync; +#endif }; // Specializations of `FormatStringFor()` diff --git a/src/core/utils/ping_sender.cpp b/src/core/utils/ping_sender.cpp index 7482968e3..96e601774 100644 --- a/src/core/utils/ping_sender.cpp +++ b/src/core/utils/ping_sender.cpp @@ -97,7 +97,7 @@ PingSender::PingSender(Instance &aInstance) Error PingSender::Ping(const Config &aConfig) { - Error error = kErrorPending; + Error error = kErrorNone; VerifyOrExit(!mTimer.IsRunning(), error = kErrorBusy); diff --git a/src/posix/cli_readline.cpp b/src/posix/cli_readline.cpp index e55dd9b8c..7fd1b0b23 100644 --- a/src/posix/cli_readline.cpp +++ b/src/posix/cli_readline.cpp @@ -56,8 +56,6 @@ #include "openthread-core-config.h" #include "platform-posix.h" -static const char sPrompt[] = "> "; - static void InputCallback(char *aLine) { if (aLine != nullptr) @@ -65,8 +63,8 @@ static void InputCallback(char *aLine) if (aLine[0] != '\0') { add_history(aLine); - otCliInputLine(aLine); } + otCliInputLine(aLine); free(aLine); } else @@ -90,7 +88,9 @@ extern "C" void otAppCliInit(otInstance *aInstance) rl_set_screen_size(0, OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH); - rl_callback_handler_install(sPrompt, InputCallback); + rl_callback_handler_install("", InputCallback); + rl_already_prompted = true; + otCliInit(aInstance, OutputCallback, nullptr); } diff --git a/src/posix/cli_stdio.cpp b/src/posix/cli_stdio.cpp index ab1473759..e6c62d7ab 100644 --- a/src/posix/cli_stdio.cpp +++ b/src/posix/cli_stdio.cpp @@ -49,7 +49,6 @@ #include "platform-posix.h" namespace { -constexpr char sPrompt[] = "> "; int OutputCallback(void *aContext, const char *aFormat, va_list aArguments) { @@ -93,7 +92,6 @@ extern "C" void otAppCliProcess(const otSysMainloopContext *aMainloop) if (fgets(buffer, sizeof(buffer), stdin) != nullptr) { otCliInputLine(buffer); - dprintf(STDOUT_FILENO, "%s", sPrompt); } else { diff --git a/src/posix/platform/daemon.cpp b/src/posix/platform/daemon.cpp index 25997768e..d6d858935 100644 --- a/src/posix/platform/daemon.cpp +++ b/src/posix/platform/daemon.cpp @@ -311,7 +311,6 @@ void Daemon::Process(const otSysMainloopContext &aContext) buffer[rval] = '\0'; otLogInfoPlat("> %s", reinterpret_cast(buffer)); otCliInputLine(reinterpret_cast(buffer)); - otCliOutputFormat("> "); } else { diff --git a/tests/scripts/expect/_common.exp b/tests/scripts/expect/_common.exp index db2d62449..3cdb89b46 100644 --- a/tests/scripts/expect/_common.exp +++ b/tests/scripts/expect/_common.exp @@ -58,6 +58,7 @@ proc wait_for {command success {failure {[\r\n]FAILURE_NOT_EXPECTED[\r\n]}}} { } proc expect_line {line} { + set timeout 10 expect -re "\[\r\n \]($line)(?=\[\r\n>\])" return $expect_out(1,string) } diff --git a/tools/otci/otci/command_handlers.py b/tools/otci/otci/command_handlers.py index dbba53e17..31a3ab00b 100644 --- a/tools/otci/otci/command_handlers.py +++ b/tools/otci/otci/command_handlers.py @@ -180,6 +180,8 @@ class OtCliCommandRunner(OTCommandHandler): def __otcli_read_routine(self): while not self.__should_close.isSet(): line = self.__otcli.readline() + logging.debug('%s: %r', self.__otcli, line) + if line.startswith('> '): line = line[2:]