[diag] add gpio diag command support (#8316)
diff --git a/examples/platforms/simulation/radio.c b/examples/platforms/simulation/radio.c
index 4d6ab88..9f9db6e 100644
--- a/examples/platforms/simulation/radio.c
+++ b/examples/platforms/simulation/radio.c
@@ -166,6 +166,13 @@
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 @@
}
}
}
+
+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 96571f9..3b1f80a 100644
--- a/include/openthread/diag.h
+++ b/include/openthread/diag.h
@@ -85,8 +85,13 @@
* @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 4e96a9c..a361239 100644
--- a/include/openthread/instance.h
+++ b/include/openthread/instance.h
@@ -53,7 +53,7 @@
* @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 9dee346..46e9444 100644
--- a/include/openthread/platform/diag.h
+++ b/include/openthread/platform/diag.h
@@ -130,6 +130,32 @@
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 7883e12..eb83bef 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<FactoryDiags::Diags>().ProcessLine(aString, aOutput, aOutputMaxLen);
+ return AsCoreType(aInstance).Get<FactoryDiags::Diags>().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 090aa03..7fa128f 100644
--- a/src/core/diags/README.md
+++ b/src/core/diags/README.md
@@ -14,6 +14,7 @@
- [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 @@
stats cleared
```
+### diag gpio get \<gpio\>
+
+Get the gpio value.
+
+```bash
+> diag gpio get 0
+1
+Done
+```
+
+### diag gpio set \<gpio\> \<value\>
+
+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 6410421..ec9a0c7 100644
--- a/src/core/diags/factory_diags.cpp
+++ b/src/core/diags/factory_diags.cpp
@@ -70,8 +70,8 @@
#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 @@
#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 @@
#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<uint32_t>(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<uint32_t>(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 @@
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<bool>(value);
+
+exit:
+ return error;
+}
+
Error Diags::ParseCmd(char *aString, uint8_t &aArgsLength, char *aArgs[])
{
Error error;
@@ -571,7 +615,7 @@
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 @@
{
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 @@
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 @@
} // 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 26ccd9e..e3c7c2a 100644
--- a/src/core/diags/factory_diags.hpp
+++ b/src/core/diags/factory_diags.hpp
@@ -68,7 +68,7 @@
* @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 @@
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 @@
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 c9eda5c..21bc329 100644
--- a/src/lib/spinel/radio_spinel_impl.hpp
+++ b/src/lib/spinel/radio_spinel_impl.hpp
@@ -790,6 +790,7 @@
{
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 aa04bc5..83df7c2 100644
--- a/src/ncp/ncp_base.cpp
+++ b/src/ncp/ncp_base.cpp
@@ -1370,7 +1370,7 @@
}
#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 8fb63bf..495cc69 100644
--- a/src/posix/platform/radio.cpp
+++ b/src/posix/platform/radio.cpp
@@ -547,6 +547,34 @@
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<bool>(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 e93cbfc..1c00d5e 100644
--- a/tests/fuzz/fuzzer_platform.cpp
+++ b/tests/fuzz/fuzzer_platform.cpp
@@ -207,12 +207,14 @@
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 0000000..6866808
--- /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 08b9553..e585d60 100755
--- a/tests/scripts/expect/posix-diag-rcp.exp
+++ b/tests/scripts/expect/posix-diag-rcp.exp
@@ -42,21 +42,15 @@
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"