From 11a24d111f16a60a241ea0c4753e7bd28a5c4c93 Mon Sep 17 00:00:00 2001 From: Zhanglong Xia Date: Tue, 1 Nov 2022 08:05:20 +0800 Subject: [PATCH] [diag] add gpio diag command support (#8316) --- examples/platforms/simulation/radio.c | 29 ++++ include/openthread/diag.h | 7 +- include/openthread/instance.h | 2 +- include/openthread/platform/diag.h | 26 ++++ src/core/api/diags_api.cpp | 4 +- src/core/diags/README.md | 22 +++ src/core/diags/factory_diags.cpp | 76 +++++++++- src/core/diags/factory_diags.hpp | 4 +- src/lib/spinel/radio_spinel_impl.hpp | 1 + src/ncp/ncp_base.cpp | 2 +- src/posix/platform/radio.cpp | 28 ++++ tests/fuzz/fuzzer_platform.cpp | 4 +- tests/scripts/expect/cli-diags.exp | 175 ++++++++++++++++++++++++ tests/scripts/expect/posix-diag-rcp.exp | 12 +- 14 files changed, 369 insertions(+), 23 deletions(-) create mode 100755 tests/scripts/expect/cli-diags.exp diff --git a/examples/platforms/simulation/radio.c b/examples/platforms/simulation/radio.c index 4d6ab8810..9f9db6ead 100644 --- a/examples/platforms/simulation/radio.c +++ b/examples/platforms/simulation/radio.c @@ -166,6 +166,13 @@ static otMacKeyMaterial sCurrKey; static otMacKeyMaterial sNextKey; static otRadioKeyType sKeyType; +enum +{ + SIM_GPIO = 0, +}; + +static bool sGpioValue = false; + static int8_t GetRssi(uint16_t aChannel); #if OPENTHREAD_SIMULATION_VIRTUAL_TIME == 0 @@ -1399,3 +1406,25 @@ void parseFromEnvAsUint16(const char *aEnvName, uint16_t *aValue) } } } + +otError otPlatDiagGpioSet(uint32_t aGpio, bool aValue) +{ + otError error = OT_ERROR_NONE; + + otEXPECT_ACTION(aGpio == SIM_GPIO, error = OT_ERROR_INVALID_ARGS); + sGpioValue = aValue; + +exit: + return error; +} + +otError otPlatDiagGpioGet(uint32_t aGpio, bool *aValue) +{ + otError error = OT_ERROR_NONE; + + otEXPECT_ACTION((aGpio == SIM_GPIO) && (aValue != NULL), error = OT_ERROR_INVALID_ARGS); + *aValue = sGpioValue; + +exit: + return error; +} diff --git a/include/openthread/diag.h b/include/openthread/diag.h index 96571f9c0..3b1f80a89 100644 --- a/include/openthread/diag.h +++ b/include/openthread/diag.h @@ -85,8 +85,13 @@ otError otDiagProcessCmd(otInstance *aInstance, * @param[out] aOutput The diagnostics execution result. * @param[in] aOutputMaxLen The output buffer size. * + * @retval OT_ERROR_NONE The command is successfully process. + * @retval OT_ERROR_INVALID_ARGS The command is supported but invalid arguments provided. + * @retval OT_ERROR_NOT_IMPLEMENTED The command is not supported. + * @retval OT_ERROR_NO_BUFS The command string is too long. + * */ -void otDiagProcessCmdLine(otInstance *aInstance, const char *aString, char *aOutput, size_t aOutputMaxLen); +otError otDiagProcessCmdLine(otInstance *aInstance, const char *aString, char *aOutput, size_t aOutputMaxLen); /** * This function indicates whether or not the factory diagnostics mode is enabled. diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 4e96a9c83..a36123922 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (256) +#define OPENTHREAD_API_VERSION (257) /** * @addtogroup api-instance diff --git a/include/openthread/platform/diag.h b/include/openthread/platform/diag.h index 9dee346c2..46e944403 100644 --- a/include/openthread/platform/diag.h +++ b/include/openthread/platform/diag.h @@ -129,6 +129,32 @@ void otPlatDiagRadioReceived(otInstance *aInstance, otRadioFrame *aFrame, otErro */ void otPlatDiagAlarmCallback(otInstance *aInstance); +/** + * This function sets the gpio value. + * + * @param[in] aGpio The gpio number. + * @param[in] aValue true to set the gpio to high level, or false otherwise. + * + * @retval OT_ERROR_NONE Successfully set the gpio. + * @retval OT_ERROR_INVALID_ARGS @p aGpio is not supported. + * @retval OT_ERROR_NOT_IMPLEMENTED This function is not implemented on the platform. + * + */ +otError otPlatDiagGpioSet(uint32_t aGpio, bool aValue); + +/** + * This function gets the gpio value. + * + * @param[in] aGpio The gpio number. + * @param[out] aValue A pointer where to put gpio value. + * + * @retval OT_ERROR_NONE Successfully got the gpio value. + * @retval OT_ERROR_INVALID_ARGS @p aGpio is not supported or @p aValue is NULL. + * @retval OT_ERROR_NOT_IMPLEMENTED This function is not implemented on the platform. + * + */ +otError otPlatDiagGpioGet(uint32_t aGpio, bool *aValue); + /** * @} * diff --git a/src/core/api/diags_api.cpp b/src/core/api/diags_api.cpp index 7883e12d2..eb83bef58 100644 --- a/src/core/api/diags_api.cpp +++ b/src/core/api/diags_api.cpp @@ -42,11 +42,11 @@ using namespace ot; -void otDiagProcessCmdLine(otInstance *aInstance, const char *aString, char *aOutput, size_t aOutputMaxLen) +otError otDiagProcessCmdLine(otInstance *aInstance, const char *aString, char *aOutput, size_t aOutputMaxLen) { AssertPointerIsNotNull(aString); - AsCoreType(aInstance).Get().ProcessLine(aString, aOutput, aOutputMaxLen); + return AsCoreType(aInstance).Get().ProcessLine(aString, aOutput, aOutputMaxLen); } otError otDiagProcessCmd(otInstance *aInstance, uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen) diff --git a/src/core/diags/README.md b/src/core/diags/README.md index 090aa0376..7fa128fd6 100644 --- a/src/core/diags/README.md +++ b/src/core/diags/README.md @@ -14,6 +14,7 @@ The diagnostics module supports common diagnostics features that are listed belo - [diag repeat](#diag-repeat-delay-length) - [diag radio](#diag-radio-sleep) - [diag stats](#diag-stats) +- [diag gpio](#diag-gpio-get-gpio) - [diag stop](#diag-stop) ### diag @@ -157,6 +158,27 @@ Clear statistics during diagnostics mode. stats cleared ``` +### diag gpio get \ + +Get the gpio value. + +```bash +> diag gpio get 0 +1 +Done +``` + +### diag gpio set \ \ + +Set the gpio value. + +The parameter `value` has to be `0` or `1`. + +```bash +> diag gpio set 0 1 +Done +``` + ### diag stop Stop diagnostics mode and print statistics. diff --git a/src/core/diags/factory_diags.cpp b/src/core/diags/factory_diags.cpp index 6410421f7..ec9a0c7cf 100644 --- a/src/core/diags/factory_diags.cpp +++ b/src/core/diags/factory_diags.cpp @@ -70,8 +70,8 @@ namespace FactoryDiags { #if OPENTHREAD_RADIO && !OPENTHREAD_RADIO_CLI const struct Diags::Command Diags::sCommands[] = { - {"channel", &Diags::ProcessChannel}, {"echo", &Diags::ProcessEcho}, {"power", &Diags::ProcessPower}, - {"start", &Diags::ProcessStart}, {"stop", &Diags::ProcessStop}, + {"channel", &Diags::ProcessChannel}, {"echo", &Diags::ProcessEcho}, {"gpio", &Diags::ProcessGpio}, + {"power", &Diags::ProcessPower}, {"start", &Diags::ProcessStart}, {"stop", &Diags::ProcessStop}, }; Diags::Diags(Instance &aInstance) @@ -181,9 +181,9 @@ extern "C" void otPlatDiagAlarmFired(otInstance *aInstance) #else // OPENTHREAD_RADIO && !OPENTHREAD_RADIO_CLI // For OPENTHREAD_FTD, OPENTHREAD_MTD, OPENTHREAD_RADIO_CLI const struct Diags::Command Diags::sCommands[] = { - {"channel", &Diags::ProcessChannel}, {"power", &Diags::ProcessPower}, {"radio", &Diags::ProcessRadio}, - {"repeat", &Diags::ProcessRepeat}, {"send", &Diags::ProcessSend}, {"start", &Diags::ProcessStart}, - {"stats", &Diags::ProcessStats}, {"stop", &Diags::ProcessStop}, + {"channel", &Diags::ProcessChannel}, {"gpio", &Diags::ProcessGpio}, {"power", &Diags::ProcessPower}, + {"radio", &Diags::ProcessRadio}, {"repeat", &Diags::ProcessRepeat}, {"send", &Diags::ProcessSend}, + {"start", &Diags::ProcessStart}, {"stats", &Diags::ProcessStats}, {"stop", &Diags::ProcessStop}, }; Diags::Diags(Instance &aInstance) @@ -543,6 +543,37 @@ exit: #endif // OPENTHREAD_RADIO +Error Diags::ProcessGpio(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen) +{ + Error error = kErrorNone; + long value; + uint32_t gpio; + bool level; + + if ((aArgsLength == 2) && (strcmp(aArgs[0], "get") == 0)) + { + SuccessOrExit(error = ParseLong(aArgs[1], value)); + gpio = static_cast(value); + SuccessOrExit(error = otPlatDiagGpioGet(gpio, &level)); + snprintf(aOutput, aOutputMaxLen, "%d\r\n", level); + } + else if ((aArgsLength == 3) && (strcmp(aArgs[0], "set") == 0)) + { + SuccessOrExit(error = ParseLong(aArgs[1], value)); + gpio = static_cast(value); + SuccessOrExit(error = ParseBool(aArgs[2], level)); + SuccessOrExit(error = otPlatDiagGpioSet(gpio, level)); + } + else + { + error = kErrorInvalidArgs; + } + +exit: + AppendErrorResult(error, aOutput, aOutputMaxLen); + return error; +} + void Diags::AppendErrorResult(Error aError, char *aOutput, size_t aOutputMaxLen) { if (aError != kErrorNone) @@ -558,6 +589,19 @@ Error Diags::ParseLong(char *aString, long &aLong) return (*endptr == '\0') ? kErrorNone : kErrorParse; } +Error Diags::ParseBool(char *aString, bool &aBool) +{ + Error error; + long value; + + SuccessOrExit(error = ParseLong(aString, value)); + VerifyOrExit((value == 0) || (value == 1), error = kErrorParse); + aBool = static_cast(value); + +exit: + return error; +} + Error Diags::ParseCmd(char *aString, uint8_t &aArgsLength, char *aArgs[]) { Error error; @@ -571,7 +615,7 @@ exit: return error; } -void Diags::ProcessLine(const char *aString, char *aOutput, size_t aOutputMaxLen) +Error Diags::ProcessLine(const char *aString, char *aOutput, size_t aOutputMaxLen) { constexpr uint16_t kMaxCommandBuffer = OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE; @@ -591,7 +635,7 @@ exit: { case kErrorNone: aOutput[0] = '\0'; // In case there is no output. - IgnoreError(ProcessCmd(argCount, &args[0], aOutput, aOutputMaxLen)); + error = ProcessCmd(argCount, &args[0], aOutput, aOutputMaxLen); break; case kErrorNoBufs: @@ -606,6 +650,8 @@ exit: snprintf(aOutput, aOutputMaxLen, "failed to parse command string\r\n"); break; } + + return error; } Error Diags::ProcessCmd(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen) @@ -663,4 +709,20 @@ bool Diags::IsEnabled(void) } // namespace FactoryDiags } // namespace ot +OT_TOOL_WEAK otError otPlatDiagGpioSet(uint32_t aGpio, bool aValue) +{ + OT_UNUSED_VARIABLE(aGpio); + OT_UNUSED_VARIABLE(aValue); + + return OT_ERROR_NOT_IMPLEMENTED; +} + +OT_TOOL_WEAK otError otPlatDiagGpioGet(uint32_t aGpio, bool *aValue) +{ + OT_UNUSED_VARIABLE(aGpio); + OT_UNUSED_VARIABLE(aValue); + + return OT_ERROR_NOT_IMPLEMENTED; +} + #endif // OPENTHREAD_CONFIG_DIAG_ENABLE diff --git a/src/core/diags/factory_diags.hpp b/src/core/diags/factory_diags.hpp index 26ccd9e83..e3c7c2a76 100644 --- a/src/core/diags/factory_diags.hpp +++ b/src/core/diags/factory_diags.hpp @@ -68,7 +68,7 @@ public: * @param[in] aOutputMaxLen The output buffer size. * */ - void ProcessLine(const char *aString, char *aOutput, size_t aOutputMaxLen); + Error ProcessLine(const char *aString, char *aOutput, size_t aOutputMaxLen); /** * This method processes a factory diagnostics command line. @@ -144,6 +144,7 @@ private: Error ParseCmd(char *aString, uint8_t &aArgsLength, char *aArgs[]); Error ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen); + Error ProcessGpio(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen); Error ProcessPower(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen); Error ProcessRadio(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen); Error ProcessRepeat(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen); @@ -159,6 +160,7 @@ private: static void AppendErrorResult(Error aError, char *aOutput, size_t aOutputMaxLen); static Error ParseLong(char *aString, long &aLong); + static Error ParseBool(char *aString, bool &aBool); static const struct Command sCommands[]; diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp index c9eda5c48..21bc3291b 100644 --- a/src/lib/spinel/radio_spinel_impl.hpp +++ b/src/lib/spinel/radio_spinel_impl.hpp @@ -790,6 +790,7 @@ void RadioSpinel::HandleWaitingResponse(uint3 { spinel_ssize_t unpacked; + mError = OT_ERROR_NONE; VerifyOrExit(mDiagOutput != nullptr); unpacked = spinel_datatype_unpack_in_place(aBuffer, aLength, SPINEL_DATATYPE_UTF8_S, mDiagOutput, &mDiagOutputMaxLen); diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index aa04bc527..83df7c202 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -1370,7 +1370,7 @@ otError NcpBase::HandlePropertySet_SPINEL_PROP_NEST_STREAM_MFG(uint8_t aHeader) } #endif - otDiagProcessCmdLine(mInstance, string, output, sizeof(output)); + SuccessOrExit(error = otDiagProcessCmdLine(mInstance, string, output, sizeof(output))); // Prepare the response SuccessOrExit(error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_NEST_STREAM_MFG)); diff --git a/src/posix/platform/radio.cpp b/src/posix/platform/radio.cpp index 8fb63bfb2..495cc69d2 100644 --- a/src/posix/platform/radio.cpp +++ b/src/posix/platform/radio.cpp @@ -547,6 +547,34 @@ exit: return; } +otError otPlatDiagGpioSet(uint32_t aGpio, bool aValue) +{ + otError error; + char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE]; + + snprintf(cmd, sizeof(cmd), "gpio set %d %d", aGpio, aValue); + SuccessOrExit(error = sRadioSpinel.PlatDiagProcess(cmd, nullptr, 0)); + +exit: + return error; +} + +otError otPlatDiagGpioGet(uint32_t aGpio, bool *aValue) +{ + otError error; + char cmd[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE]; + char output[OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE]; + char * str; + + snprintf(cmd, sizeof(cmd), "gpio get %d", aGpio); + SuccessOrExit(error = sRadioSpinel.PlatDiagProcess(cmd, output, sizeof(output))); + VerifyOrExit((str = strtok(output, "\r")) != nullptr, error = OT_ERROR_FAILED); + *aValue = static_cast(atoi(str)); + +exit: + return error; +} + void otPlatDiagRadioReceived(otInstance *aInstance, otRadioFrame *aFrame, otError aError) { OT_UNUSED_VARIABLE(aInstance); diff --git a/tests/fuzz/fuzzer_platform.cpp b/tests/fuzz/fuzzer_platform.cpp index e93cbfc5f..1c00d5e0d 100644 --- a/tests/fuzz/fuzzer_platform.cpp +++ b/tests/fuzz/fuzzer_platform.cpp @@ -207,12 +207,14 @@ otError otDiagProcessCmd(otInstance *aInstance, uint8_t aArgsLength, char *aArgs return OT_ERROR_NOT_IMPLEMENTED; } -void otDiagProcessCmdLine(otInstance *aInstance, const char *aString, char *aOutput, size_t aOutputMaxLen) +otError otDiagProcessCmdLine(otInstance *aInstance, const char *aString, char *aOutput, size_t aOutputMaxLen) { OT_UNUSED_VARIABLE(aInstance); OT_UNUSED_VARIABLE(aString); OT_UNUSED_VARIABLE(aOutput); OT_UNUSED_VARIABLE(aOutputMaxLen); + + return OT_ERROR_NOT_IMPLEMENTED; } void otPlatReset(otInstance *aInstance) diff --git a/tests/scripts/expect/cli-diags.exp b/tests/scripts/expect/cli-diags.exp new file mode 100755 index 000000000..686680862 --- /dev/null +++ b/tests/scripts/expect/cli-diags.exp @@ -0,0 +1,175 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2022, 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. +# + +source "tests/scripts/expect/_common.exp" +source "tests/scripts/expect/_multinode.exp" + +spawn_node 1 +spawn_node 2 + +switch_node 1 +send "diag start\n" +expect "start diagnostics mode" +expect "status 0x00" +expect_line "Done" + +send "diag channel 11\n" +expect "set channel to 11" +expect "status 0x00" +expect_line "Done" + +send "diag stats clear\n" +expect "stats cleared" +expect_line "Done" + +switch_node 2 + +send "diag start\n" +expect "start diagnostics mode" +expect "status 0x00" +expect_line "Done" + +send "diag channel 11\n" +expect "set channel to 11" +expect "status 0x00" +expect_line "Done" + +send "diag stats clear\n" +expect "stats cleared" +expect_line "Done" + +send "diag send 10 100\n" +expect "sending 0xa packet(s), length 0x64" +expect "status 0x00" +expect_line "Done" + +sleep 2 + +send "diag stats\n" +expect "received packets: 0" +expect "sent packets: 10" +expect "first received packet: rssi=0, lqi=0" +expect "last received packet: rssi=0, lqi=0" +expect_line "Done" + +switch_node 1 + +send "diag stats\n" +expect "received packets: 10" +expect "sent packets: 0" +expect "first received packet: rssi=-20, lqi=0" +expect "last received packet: rssi=-20, lqi=0" +expect_line "Done" + +send "diag stats clear\n" +expect "stats cleared" +expect_line "Done" + +switch_node 2 + +send "diag repeat 20 100\n" +expect "sending packets of length 0x64 at the delay of 0x14 ms" +expect "status 0x00" +expect_line "Done" +sleep 1 +send "diag repeat stop\n" +expect "repeated packet transmission is stopped" +expect "status 0x00" +expect_line "Done" + +switch_node 1 + +send "diag stats\n" +expect -r {received packets: \d+} +expect "sent packets: 0" +expect "first received packet: rssi=-20, lqi=0" +expect "last received packet: rssi=-20, lqi=0" +expect_line "Done" + +send "diag stats clear\n" +expect "stats cleared" +expect_line "Done" + +dispose_all + + +spawn_node 1 + +send "diag start\n" +expect "start diagnostics mode" +expect "status 0x00" +expect_line "Done" + +send "diag channel 11\n" +expect "set channel to 11" +expect "status 0x00" +expect_line "Done" + +send "diag power 10\n" +expect "set tx power to 10 dBm" +expect "status 0x00" +expect_line "Done" + +send "diag radio sleep\n" +expect "set radio from receive to sleep" +expect "status 0x00" +expect_line "Done" + +send "diag radio state\n" +expect "sleep" +expect_line "Done" + +send "diag radio receive\n" +expect "set radio from sleep to receive on channel 11" +expect "status 0x00" +expect_line "Done" + +send "diag radio state\n" +expect "receive" +expect_line "Done" + +send "diag gpio set 0 1\n" +expect_line "Done" + +send "diag gpio get 0\n" +expect "1" +expect_line "Done" + +send "diag invalid_commad\n" +expect "Error 35: InvalidCommand" + +send "diag stop\n" +expect_line "Done" + +send "diag channel\n" +expect "failed" +expect "status 0xd" +expect "Error 13: InvalidState" + +dispose_all diff --git a/tests/scripts/expect/posix-diag-rcp.exp b/tests/scripts/expect/posix-diag-rcp.exp index 08b9553e3..e585d60b4 100755 --- a/tests/scripts/expect/posix-diag-rcp.exp +++ b/tests/scripts/expect/posix-diag-rcp.exp @@ -42,21 +42,15 @@ send "diag rcp\n" expect "diagnostics mode is enabled" expect_line "Done" send "diag rcp channel\n" -expect "failed" -expect "status 0x7" -expect_line "Done" +expect "Error 7: InvalidArgs" send "diag rcp channel 11\n" expect_line "Done" send "diag rcp power\n" -expect "failed" -expect "status 0x7" -expect_line "Done" +expect "Error 7: InvalidArgs" send "diag rcp power 10\n" expect_line "Done" send "diag rcp echo\n" -expect "failed" -expect "status 0x7" -expect_line "Done" +expect "Error 7: InvalidArgs" send "diag rcp echo 0123456789\n" expect_line "0123456789" expect_line "Done"