/* * Copyright (c) 2016, 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 CLI interpreter. */ #include "cli.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "common/num_utils.hpp" #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE #include #endif #if OPENTHREAD_FTD #include #include #endif #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE #include #endif #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE #include #endif #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE #include #endif #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) #include #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE #include #endif #endif #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ (OPENTHREAD_FTD || \ (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) #include #endif #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE #include #endif #if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_DEBUG_UART) && OPENTHREAD_POSIX #include #endif #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE #include #endif #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE || OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE #include #endif #if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE && (OPENTHREAD_FTD || OPENTHREAD_MTD) #include #endif #include "common/new.hpp" #include "common/numeric_limits.hpp" #include "common/string.hpp" #include "mac/channel_mask.hpp" namespace ot { namespace Cli { Interpreter *Interpreter::sInterpreter = nullptr; static OT_DEFINE_ALIGNED_VAR(sInterpreterRaw, sizeof(Interpreter), uint64_t); Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, void *aContext) : OutputImplementer(aCallback, aContext) , Utils(aInstance, *this) , mCommandIsPending(false) , mInternalDebugCommand(false) , mTimer(*aInstance, HandleTimer, this) #if OPENTHREAD_FTD || OPENTHREAD_MTD #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE , mSntpQueryingInProgress(false) #endif , mDataset(aInstance, *this) , mNetworkData(aInstance, *this) , mUdp(aInstance, *this) #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE , mMacFilter(aInstance, *this) #endif #if OPENTHREAD_CLI_DNS_ENABLE , mDns(aInstance, *this) #endif #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE && OPENTHREAD_CONFIG_MULTICAST_DNS_PUBLIC_API_ENABLE , mMdns(aInstance, *this) #endif #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) , mBbr(aInstance, *this) #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE , mBr(aInstance, *this) #endif #if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE , mTcp(aInstance, *this) #endif #if OPENTHREAD_CONFIG_COAP_API_ENABLE , mCoap(aInstance, *this) #endif #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE , mCoapSecure(aInstance, *this) #endif #if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD , mCommissioner(aInstance, *this) #endif #if OPENTHREAD_CONFIG_JOINER_ENABLE , mJoiner(aInstance, *this) #endif #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE , mSrpClient(aInstance, *this) #endif #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE , mSrpServer(aInstance, *this) #endif #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE , mHistory(aInstance, *this) #endif #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE , mLinkMetrics(aInstance, *this) #endif #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_ENABLE , mTcat(aInstance, *this) #endif #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE , mPing(aInstance, *this) #endif #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE , mLocateInProgress(false) #endif #endif // OPENTHREAD_FTD || OPENTHREAD_MTD { #if (OPENTHREAD_FTD || OPENTHREAD_MTD) && OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK otIp6SetReceiveCallback(GetInstancePtr(), &Interpreter::HandleIp6Receive, this); #endif ClearAllBytes(mUserCommands); OutputPrompt(); } void Interpreter::OutputResult(otError aError) { if (mInternalDebugCommand) { if (aError != OT_ERROR_NONE) { OutputLine("Error %u: %s", aError, otThreadErrorToString(aError)); } ExitNow(); } OT_ASSERT(mCommandIsPending); VerifyOrExit(aError != OT_ERROR_PENDING); if (aError == OT_ERROR_NONE) { OutputLine("Done"); } else { OutputLine("Error %u: %s", aError, otThreadErrorToString(aError)); } mCommandIsPending = false; mTimer.Stop(); OutputPrompt(); exit: return; } #if OPENTHREAD_CONFIG_DIAG_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error; char *args[kMaxArgs]; char output[OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE]; // all diagnostics related features are processed within diagnostics module Arg::CopyArgsToStringArray(aArgs, args); error = otDiagProcessCmd(GetInstancePtr(), Arg::GetArgsLength(aArgs), args, output, sizeof(output)); OutputFormat("%s", output); return error; } #endif template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli version * @code * version * OPENTHREAD/gf4f2f04; Jul 1 2016 17:00:09 * Done * @endcode * @par api_copy * #otGetVersionString */ if (aArgs[0].IsEmpty()) { OutputLine("%s", otGetVersionString()); } /** * @cli version api * @code * version api * 28 * Done * @endcode * @par * Prints the API version number. */ else if (aArgs[0] == "api") { OutputLine("%u", OPENTHREAD_API_VERSION); } else { error = OT_ERROR_INVALID_COMMAND; } return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; if (aArgs[0].IsEmpty()) { otInstanceReset(GetInstancePtr()); } #if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE /** * @cli reset bootloader * @code * reset bootloader * @endcode * @cparam reset bootloader * @par api_copy * #otInstanceResetToBootloader */ else if (aArgs[0] == "bootloader") { error = otInstanceResetToBootloader(GetInstancePtr()); } #endif else { error = OT_ERROR_INVALID_COMMAND; } return error; } void Interpreter::ProcessLine(char *aBuf) { Arg args[kMaxArgs + 1]; otError error = OT_ERROR_NONE; OT_ASSERT(aBuf != nullptr); if (!mInternalDebugCommand) { // 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); } SuccessOrExit(error = ot::Utils::CmdLineParser::ParseCmd(aBuf, args, kMaxArgs)); VerifyOrExit(!args[0].IsEmpty(), mCommandIsPending = false); if (!mInternalDebugCommand) { LogInput(args); #if OPENTHREAD_CONFIG_DIAG_ENABLE if (otDiagIsEnabled(GetInstancePtr()) && (args[0] != "diag") && (args[0] != "factoryreset")) { OutputLine("under diagnostics mode, execute 'diag stop' before running any other commands."); ExitNow(error = OT_ERROR_INVALID_STATE); } #endif } error = ProcessCommand(args); exit: if ((error != OT_ERROR_NONE) || !args[0].IsEmpty()) { OutputResult(error); } else if (!mCommandIsPending) { OutputPrompt(); } } otError Interpreter::ProcessUserCommands(Arg aArgs[]) { otError error = OT_ERROR_INVALID_COMMAND; for (const UserCommandsEntry &entry : mUserCommands) { for (uint8_t i = 0; i < entry.mLength; i++) { if (aArgs[0] == entry.mCommands[i].mName) { char *args[kMaxArgs]; Arg::CopyArgsToStringArray(aArgs, args); error = entry.mCommands[i].mCommand(entry.mContext, Arg::GetArgsLength(aArgs) - 1, args + 1); break; } } } return error; } otError Interpreter::SetUserCommands(const otCliCommand *aCommands, uint8_t aLength, void *aContext) { otError error = OT_ERROR_FAILED; for (UserCommandsEntry &entry : mUserCommands) { if (entry.mCommands == nullptr) { entry.mCommands = aCommands; entry.mLength = aLength; entry.mContext = aContext; error = OT_ERROR_NONE; break; } } return error; } #if OPENTHREAD_FTD || OPENTHREAD_MTD #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mHistory.Process(aArgs); } #endif #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli ba port * @code * ba port * 49153 * Done * @endcode * @par api_copy * #otBorderAgentGetUdpPort */ if (aArgs[0] == "port") { OutputLine("%hu", otBorderAgentGetUdpPort(GetInstancePtr())); } /** * @cli ba state * @code * ba state * Started * Done * @endcode * @par api_copy * #otBorderAgentGetState */ else if (aArgs[0] == "state") { static const char *const kStateStrings[] = { "Stopped", // (0) OT_BORDER_AGENT_STATE_STOPPED "Started", // (1) OT_BORDER_AGENT_STATE_STARTED "Active", // (2) OT_BORDER_AGENT_STATE_ACTIVE }; static_assert(0 == OT_BORDER_AGENT_STATE_STOPPED, "OT_BORDER_AGENT_STATE_STOPPED value is incorrect"); static_assert(1 == OT_BORDER_AGENT_STATE_STARTED, "OT_BORDER_AGENT_STATE_STARTED value is incorrect"); static_assert(2 == OT_BORDER_AGENT_STATE_ACTIVE, "OT_BORDER_AGENT_STATE_ACTIVE value is incorrect"); OutputLine("%s", Stringify(otBorderAgentGetState(GetInstancePtr()), kStateStrings)); } #if OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE /** * @cli ba id (get,set) * @code * ba id * cb6da1e0c0448aaec39fa90f3d58f45c * Done * @endcode * @code * ba id 00112233445566778899aabbccddeeff * Done * @endcode * @cparam ba id [@ca{border-agent-id}] * Use the optional `border-agent-id` argument to set the Border Agent ID. * @par * Gets or sets the 16 bytes Border Router ID which can uniquely identifies the device among multiple BRs. * @sa otBorderAgentGetId * @sa otBorderAgentSetId */ else if (aArgs[0] == "id") { otBorderAgentId id; if (aArgs[1].IsEmpty()) { SuccessOrExit(error = otBorderAgentGetId(GetInstancePtr(), &id)); OutputBytesLine(id.mId); } else { uint16_t idLength = sizeof(id); SuccessOrExit(error = aArgs[1].ParseAsHexString(idLength, id.mId)); VerifyOrExit(idLength == sizeof(id), error = OT_ERROR_INVALID_ARGS); error = otBorderAgentSetId(GetInstancePtr(), &id); } } #endif // OPENTHREAD_CONFIG_BORDER_AGENT_ID_ENABLE #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE else if (aArgs[0] == "ephemeralkey") { /** * @cli ba ephemeralkey * @code * ba ephemeralkey * active * Done * @endcode * @par api_copy * #otBorderAgentIsEphemeralKeyActive */ if (aArgs[1].IsEmpty()) { OutputLine("%sactive", otBorderAgentIsEphemeralKeyActive(GetInstancePtr()) ? "" : "in"); } /** * @cli ba ephemeralkey set [timeout-in-msec] [port] * @code * ba ephemeralkey set Z10X20g3J15w1000P60m16 5000 1234 * Done * @endcode * @par api_copy * #otBorderAgentSetEphemeralKey */ else if (aArgs[1] == "set") { uint32_t timeout = 0; uint16_t port = 0; VerifyOrExit(!aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); if (!aArgs[3].IsEmpty()) { SuccessOrExit(error = aArgs[3].ParseAsUint32(timeout)); } if (!aArgs[4].IsEmpty()) { SuccessOrExit(error = aArgs[4].ParseAsUint16(port)); } error = otBorderAgentSetEphemeralKey(GetInstancePtr(), aArgs[2].GetCString(), timeout, port); } /** * @cli ba ephemeralkey clear * @code * ba ephemeralkey clear * Done * @endcode * @par api_copy * #otBorderAgentClearEphemeralKey */ else if (aArgs[1] == "clear") { otBorderAgentClearEphemeralKey(GetInstancePtr()); } /** * @cli ba ephemeralkey callback (enable, disable) * @code * ba ephemeralkey callback enable * Done * ba ephemeralkey set W10X1 5000 49155 * Done * BorderAgent callback: Ephemeral key active, port:49155 * BorderAgent callback: Ephemeral key inactive * @endcode * @par api_copy * #otBorderAgentSetEphemeralKeyCallback */ else if (aArgs[1] == "callback") { bool enable; SuccessOrExit(error = ParseEnableOrDisable(aArgs[2], enable)); if (enable) { otBorderAgentSetEphemeralKeyCallback(GetInstancePtr(), HandleBorderAgentEphemeralKeyStateChange, this); } else { otBorderAgentSetEphemeralKeyCallback(GetInstancePtr(), nullptr, nullptr); } } else { error = OT_ERROR_INVALID_ARGS; } } #endif // OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE else { ExitNow(error = OT_ERROR_INVALID_COMMAND); } exit: return error; } #if OPENTHREAD_CONFIG_BORDER_AGENT_EPHEMERAL_KEY_ENABLE void Interpreter::HandleBorderAgentEphemeralKeyStateChange(void *aContext) { reinterpret_cast(aContext)->HandleBorderAgentEphemeralKeyStateChange(); } void Interpreter::HandleBorderAgentEphemeralKeyStateChange(void) { bool active = otBorderAgentIsEphemeralKeyActive(GetInstancePtr()); OutputFormat("BorderAgent callback: Ephemeral key %sactive", active ? "" : "in"); if (active) { OutputFormat(", port:%u", otBorderAgentGetUdpPort(GetInstancePtr())); } OutputNewLine(); } #endif #endif // OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mBr.Process(aArgs); } #endif #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE || OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; if (aArgs[0].IsEmpty()) { ExitNow(error = OT_ERROR_INVALID_COMMAND); } /** * @cli nat64 (enable,disable) * @code * nat64 enable * Done * @endcode * @code * nat64 disable * Done * @endcode * @cparam nat64 @ca{enable|disable} * @par api_copy * #otNat64SetEnabled * */ if (ProcessEnableDisable(aArgs, otNat64SetEnabled) == OT_ERROR_NONE) { } /** * @cli nat64 state * @code * nat64 state * PrefixManager: Active * Translator: Active * Done * @endcode * @par * Gets the state of NAT64 functions. * @par * `PrefixManager` state is available when `OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE` is enabled. * `Translator` state is available when `OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE` is enabled. * @par * When `OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE` is enabled, `PrefixManager` returns one of the following * states: * - `Disabled`: NAT64 prefix manager is disabled. * - `NotRunning`: NAT64 prefix manager is enabled, but is not running. This could mean that the routing manager is * disabled. * - `Idle`: NAT64 prefix manager is enabled and is running, but is not publishing a NAT64 prefix. This can happen * when there is another border router publishing a NAT64 prefix with a higher priority. * - `Active`: NAT64 prefix manager is enabled, running, and publishing a NAT64 prefix. * @par * When `OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE` is enabled, `Translator` returns one of the following states: * - `Disabled`: NAT64 translator is disabled. * - `NotRunning`: NAT64 translator is enabled, but is not translating packets. This could mean that the Translator * is not configured with a NAT64 prefix or a CIDR for NAT64. * - `Active`: NAT64 translator is enabled and is translating packets. * @sa otNat64GetPrefixManagerState * @sa otNat64GetTranslatorState * */ else if (aArgs[0] == "state") { static const char *const kNat64State[] = {"Disabled", "NotRunning", "Idle", "Active"}; static_assert(0 == OT_NAT64_STATE_DISABLED, "OT_NAT64_STATE_DISABLED value is incorrect"); static_assert(1 == OT_NAT64_STATE_NOT_RUNNING, "OT_NAT64_STATE_NOT_RUNNING value is incorrect"); static_assert(2 == OT_NAT64_STATE_IDLE, "OT_NAT64_STATE_IDLE value is incorrect"); static_assert(3 == OT_NAT64_STATE_ACTIVE, "OT_NAT64_STATE_ACTIVE value is incorrect"); #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE OutputLine("PrefixManager: %s", kNat64State[otNat64GetPrefixManagerState(GetInstancePtr())]); #endif #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE OutputLine("Translator: %s", kNat64State[otNat64GetTranslatorState(GetInstancePtr())]); #endif } #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE else if (aArgs[0] == "cidr") { otIp4Cidr cidr; /** * @cli nat64 cidr * @code * nat64 cidr * 192.168.255.0/24 * Done * @endcode * @par api_copy * #otNat64GetCidr * */ if (aArgs[1].IsEmpty()) { char cidrString[OT_IP4_CIDR_STRING_SIZE]; SuccessOrExit(error = otNat64GetCidr(GetInstancePtr(), &cidr)); otIp4CidrToString(&cidr, cidrString, sizeof(cidrString)); OutputLine("%s", cidrString); } /** * @cli nat64 cidr * @code * nat64 cidr 192.168.255.0/24 * Done * @endcode * @par api_copy * #otPlatNat64SetIp4Cidr * */ else { SuccessOrExit(error = otIp4CidrFromString(aArgs[1].GetCString(), &cidr)); error = otNat64SetIp4Cidr(GetInstancePtr(), &cidr); } } /** * @cli nat64 mappings * @code * nat64 mappings * | | Address | | 4 to 6 | 6 to 4 | * +----------+---------------------------+--------+--------------+--------------+ * | ID | IPv6 | IPv4 | Expiry | Pkts | Bytes | Pkts | Bytes | * +----------+------------+--------------+--------+------+-------+------+-------+ * | 00021cb9 | fdc7::df79 | 192.168.64.2 | 7196s | 6 | 456 | 11 | 1928 | * | | TCP | 0 | 0 | 0 | 0 | * | | UDP | 1 | 136 | 16 | 1608 | * | | ICMP | 5 | 320 | 5 | 320 | * @endcode * @par api_copy * #otNat64GetNextAddressMapping * */ else if (aArgs[0] == "mappings") { static const char *const kNat64StatusLevel1Title[] = {"", "Address", "", "4 to 6", "6 to 4"}; static const uint8_t kNat64StatusLevel1ColumnWidths[] = { 18, 61, 8, 25, 25, }; static const char *const kNat64StatusTableHeader[] = { "ID", "IPv6", "IPv4", "Expiry", "Pkts", "Bytes", "Pkts", "Bytes", }; static const uint8_t kNat64StatusTableColumnWidths[] = { 18, 42, 18, 8, 10, 14, 10, 14, }; otNat64AddressMappingIterator iterator; otNat64AddressMapping mapping; OutputTableHeader(kNat64StatusLevel1Title, kNat64StatusLevel1ColumnWidths); OutputTableHeader(kNat64StatusTableHeader, kNat64StatusTableColumnWidths); otNat64InitAddressMappingIterator(GetInstancePtr(), &iterator); while (otNat64GetNextAddressMapping(GetInstancePtr(), &iterator, &mapping) == OT_ERROR_NONE) { char ip4AddressString[OT_IP4_ADDRESS_STRING_SIZE]; char ip6AddressString[OT_IP6_PREFIX_STRING_SIZE]; otIp6AddressToString(&mapping.mIp6, ip6AddressString, sizeof(ip6AddressString)); otIp4AddressToString(&mapping.mIp4, ip4AddressString, sizeof(ip4AddressString)); OutputFormat("| %08lx%08lx ", ToUlong(static_cast(mapping.mId >> 32)), ToUlong(static_cast(mapping.mId & 0xffffffff))); OutputFormat("| %40s ", ip6AddressString); OutputFormat("| %16s ", ip4AddressString); OutputFormat("| %5lus ", ToUlong(mapping.mRemainingTimeMs / 1000)); OutputNat64Counters(mapping.mCounters.mTotal); OutputFormat("| %16s ", ""); OutputFormat("| %68s ", "TCP"); OutputNat64Counters(mapping.mCounters.mTcp); OutputFormat("| %16s ", ""); OutputFormat("| %68s ", "UDP"); OutputNat64Counters(mapping.mCounters.mUdp); OutputFormat("| %16s ", ""); OutputFormat("| %68s ", "ICMP"); OutputNat64Counters(mapping.mCounters.mIcmp); } } /** * @cli nat64 counters * @code * nat64 counters * | | 4 to 6 | 6 to 4 | * +---------------+-------------------------+-------------------------+ * | Protocol | Pkts | Bytes | Pkts | Bytes | * +---------------+----------+--------------+----------+--------------+ * | Total | 11 | 704 | 11 | 704 | * | TCP | 0 | 0 | 0 | 0 | * | UDP | 0 | 0 | 0 | 0 | * | ICMP | 11 | 704 | 11 | 704 | * | Errors | Pkts | Pkts | * +---------------+-------------------------+-------------------------+ * | Total | 8 | 4 | * | Illegal Pkt | 0 | 0 | * | Unsup Proto | 0 | 0 | * | No Mapping | 2 | 0 | * Done * @endcode * @par * Gets the NAT64 translator packet and error counters. * @par * Available when `OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE` is enabled. * @sa otNat64GetCounters * @sa otNat64GetErrorCounters * */ else if (aArgs[0] == "counters") { static const char *const kNat64CounterTableHeader[] = { "", "4 to 6", "6 to 4", }; static const uint8_t kNat64CounterTableHeaderColumns[] = {15, 25, 25}; static const char *const kNat64CounterTableSubHeader[] = { "Protocol", "Pkts", "Bytes", "Pkts", "Bytes", }; static const uint8_t kNat64CounterTableSubHeaderColumns[] = { 15, 10, 14, 10, 14, }; static const char *const kNat64CounterTableErrorSubHeader[] = { "Errors", "Pkts", "Pkts", }; static const uint8_t kNat64CounterTableErrorSubHeaderColumns[] = { 15, 25, 25, }; static const char *const kNat64CounterErrorType[] = { "Unknown", "Illegal Pkt", "Unsup Proto", "No Mapping", }; otNat64ProtocolCounters counters; otNat64ErrorCounters errorCounters; Uint64StringBuffer u64StringBuffer; OutputTableHeader(kNat64CounterTableHeader, kNat64CounterTableHeaderColumns); OutputTableHeader(kNat64CounterTableSubHeader, kNat64CounterTableSubHeaderColumns); otNat64GetCounters(GetInstancePtr(), &counters); otNat64GetErrorCounters(GetInstancePtr(), &errorCounters); OutputFormat("| %13s ", "Total"); OutputNat64Counters(counters.mTotal); OutputFormat("| %13s ", "TCP"); OutputNat64Counters(counters.mTcp); OutputFormat("| %13s ", "UDP"); OutputNat64Counters(counters.mUdp); OutputFormat("| %13s ", "ICMP"); OutputNat64Counters(counters.mIcmp); OutputTableHeader(kNat64CounterTableErrorSubHeader, kNat64CounterTableErrorSubHeaderColumns); for (uint8_t i = 0; i < OT_NAT64_DROP_REASON_COUNT; i++) { OutputFormat("| %13s | %23s ", kNat64CounterErrorType[i], Uint64ToString(errorCounters.mCount4To6[i], u64StringBuffer)); OutputLine("| %23s |", Uint64ToString(errorCounters.mCount6To4[i], u64StringBuffer)); } } #endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE else { ExitNow(error = OT_ERROR_INVALID_COMMAND); } exit: return error; } #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE void Interpreter::OutputNat64Counters(const otNat64Counters &aCounters) { Uint64StringBuffer u64StringBuffer; OutputFormat("| %8s ", Uint64ToString(aCounters.m4To6Packets, u64StringBuffer)); OutputFormat("| %12s ", Uint64ToString(aCounters.m4To6Bytes, u64StringBuffer)); OutputFormat("| %8s ", Uint64ToString(aCounters.m6To4Packets, u64StringBuffer)); OutputLine("| %12s |", Uint64ToString(aCounters.m6To4Bytes, u64StringBuffer)); } #endif #endif // OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE || OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) template <> otError Interpreter::Process(Arg aArgs[]) { return mBbr.Process(aArgs); } /** * @cli domainname * @code * domainname * Thread * Done * @endcode * @par api_copy * #otThreadGetDomainName */ template <> otError Interpreter::Process(Arg aArgs[]) { /** * @cli domainname (set) * @code * domainname Test\ Thread * Done * @endcode * @cparam domainname @ca{name} * Use a `backslash` to escape spaces. * @par api_copy * #otThreadSetDomainName */ return ProcessGetSet(aArgs, otThreadGetDomainName, otThreadSetDomainName); } #if OPENTHREAD_CONFIG_DUA_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli dua iid * @code * dua iid * 0004000300020001 * Done * @endcode * @par api_copy * #otThreadGetFixedDuaInterfaceIdentifier */ if (aArgs[0] == "iid") { if (aArgs[1].IsEmpty()) { const otIp6InterfaceIdentifier *iid = otThreadGetFixedDuaInterfaceIdentifier(GetInstancePtr()); if (iid != nullptr) { OutputBytesLine(iid->mFields.m8); } } /** * @cli dua iid (set,clear) * @code * dua iid 0004000300020001 * Done * @endcode * @code * dua iid clear * Done * @endcode * @cparam dua iid @ca{iid|clear} * `dua iid clear` passes a `nullptr` to #otThreadSetFixedDuaInterfaceIdentifier. * Otherwise, you can pass the `iid`. * @par api_copy * #otThreadSetFixedDuaInterfaceIdentifier */ else if (aArgs[1] == "clear") { error = otThreadSetFixedDuaInterfaceIdentifier(GetInstancePtr(), nullptr); } else { otIp6InterfaceIdentifier iid; SuccessOrExit(error = aArgs[1].ParseAsHexString(iid.mFields.m8)); error = otThreadSetFixedDuaInterfaceIdentifier(GetInstancePtr(), &iid); } } else { error = OT_ERROR_INVALID_COMMAND; } exit: return error; } #endif // OPENTHREAD_CONFIG_DUA_ENABLE #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) /** * @cli bufferinfo * @code * bufferinfo * total: 40 * free: 40 * max-used: 5 * 6lo send: 0 0 0 * 6lo reas: 0 0 0 * ip6: 0 0 0 * mpl: 0 0 0 * mle: 0 0 0 * coap: 0 0 0 * coap secure: 0 0 0 * application coap: 0 0 0 * Done * @endcode * @par * Gets the current message buffer information. * * `total` displays the total number of message buffers in pool. * * `free` displays the number of free message buffers. * * `max-used` displays max number of used buffers at the same time since OT stack * initialization or last `bufferinfo reset`. * @par * Next, the CLI displays info about different queues used by the OpenThread stack, * for example `6lo send`. Each line after the queue represents info about a queue: * * The first number shows number messages in the queue. * * The second number shows number of buffers used by all messages in the queue. * * The third number shows total number of bytes of all messages in the queue. * @sa otMessageGetBufferInfo */ template <> otError Interpreter::Process(Arg aArgs[]) { struct BufferInfoName { const otMessageQueueInfo otBufferInfo::*mQueuePtr; const char *mName; }; static const BufferInfoName kBufferInfoNames[] = { {&otBufferInfo::m6loSendQueue, "6lo send"}, {&otBufferInfo::m6loReassemblyQueue, "6lo reas"}, {&otBufferInfo::mIp6Queue, "ip6"}, {&otBufferInfo::mMplQueue, "mpl"}, {&otBufferInfo::mMleQueue, "mle"}, {&otBufferInfo::mCoapQueue, "coap"}, {&otBufferInfo::mCoapSecureQueue, "coap secure"}, {&otBufferInfo::mApplicationCoapQueue, "application coap"}, }; otError error = OT_ERROR_NONE; if (aArgs[0].IsEmpty()) { otBufferInfo bufferInfo; otMessageGetBufferInfo(GetInstancePtr(), &bufferInfo); OutputLine("total: %u", bufferInfo.mTotalBuffers); OutputLine("free: %u", bufferInfo.mFreeBuffers); OutputLine("max-used: %u", bufferInfo.mMaxUsedBuffers); for (const BufferInfoName &info : kBufferInfoNames) { OutputLine("%s: %u %u %lu", info.mName, (bufferInfo.*info.mQueuePtr).mNumMessages, (bufferInfo.*info.mQueuePtr).mNumBuffers, ToUlong((bufferInfo.*info.mQueuePtr).mTotalBytes)); } } /** * @cli bufferinfo reset * @code * bufferinfo reset * Done * @endcode * @par api_copy * #otMessageResetBufferInfo */ else if (aArgs[0] == "reset") { otMessageResetBufferInfo(GetInstancePtr()); } else { error = OT_ERROR_INVALID_ARGS; } return error; } /** * @cli ccathreshold (get,set) * @code * ccathreshold * -75 dBm * Done * @endcode * @code * ccathreshold -62 * Done * @endcode * @cparam ccathreshold [@ca{CCA-threshold-dBm}] * Use the optional `CCA-threshold-dBm` argument to set the CCA threshold. * @par * Gets or sets the CCA threshold in dBm measured at the antenna connector per * IEEE 802.15.4 - 2015 section 10.1.4. * @sa otPlatRadioGetCcaEnergyDetectThreshold * @sa otPlatRadioSetCcaEnergyDetectThreshold */ template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; int8_t cca; if (aArgs[0].IsEmpty()) { SuccessOrExit(error = otPlatRadioGetCcaEnergyDetectThreshold(GetInstancePtr(), &cca)); OutputLine("%d dBm", cca); } else { SuccessOrExit(error = aArgs[0].ParseAsInt8(cca)); error = otPlatRadioSetCcaEnergyDetectThreshold(GetInstancePtr(), cca); } exit: return error; } #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessEnableDisable(aArgs, otThreadSetCcmEnabled); } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli test tmforiginfilter * @code * test tmforiginfilter * Enabled * @endcode * @code * test tmforiginfilter enable * Done * @endcode * @code * test tmforiginfilter disable * Done * @endcode * @cparam test tmforiginfilter [@ca{enable|disable}] * @par * Enables or disables the filter to drop TMF UDP messages from untrusted origin. * @par * By default the filter that drops TMF UDP messages from untrusted origin * is enabled. When disabled, UDP messages sent to the TMF port that originate * from untrusted origin (such as host, CLI or an external IPv6 node) will be * allowed. * @note `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is required. */ if (aArgs[0] == "tmforiginfilter") { error = ProcessEnableDisable(aArgs + 1, otThreadIsTmfOriginFilterEnabled, otThreadSetTmfOriginFilterEnabled); } return error; } /** * @cli tvcheck (enable,disable) * @code * tvcheck enable * Done * @endcode * @code * tvcheck disable * Done * @endcode * @par * Enables or disables the Thread version check when upgrading to router or leader. * This check is enabled by default. * @note `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is required. * @sa otThreadSetThreadVersionCheckEnabled */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessEnableDisable(aArgs, otThreadSetThreadVersionCheckEnabled); } #endif /** * @cli channel (get,set) * @code * channel * 11 * Done * @endcode * @code * channel 11 * Done * @endcode * @cparam channel [@ca{channel-num}] * Use `channel-num` to set the channel. * @par * Gets or sets the IEEE 802.15.4 Channel value. */ template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli channel supported * @code * channel supported * 0x7fff800 * Done * @endcode * @par api_copy * #otPlatRadioGetSupportedChannelMask */ if (aArgs[0] == "supported") { OutputLine("0x%lx", ToUlong(otPlatRadioGetSupportedChannelMask(GetInstancePtr()))); } /** * @cli channel preferred * @code * channel preferred * 0x7fff800 * Done * @endcode * @par api_copy * #otPlatRadioGetPreferredChannelMask */ else if (aArgs[0] == "preferred") { OutputLine("0x%lx", ToUlong(otPlatRadioGetPreferredChannelMask(GetInstancePtr()))); } #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE /** * @cli channel monitor * @code * channel monitor * enabled: 1 * interval: 41000 * threshold: -75 * window: 960 * count: 10552 * occupancies: * ch 11 (0x0cb7) 4.96% busy * ch 12 (0x2e2b) 18.03% busy * ch 13 (0x2f54) 18.48% busy * ch 14 (0x0fef) 6.22% busy * ch 15 (0x1536) 8.28% busy * ch 16 (0x1746) 9.09% busy * ch 17 (0x0b8b) 4.50% busy * ch 18 (0x60a7) 37.75% busy * ch 19 (0x0810) 3.14% busy * ch 20 (0x0c2a) 4.75% busy * ch 21 (0x08dc) 3.46% busy * ch 22 (0x101d) 6.29% busy * ch 23 (0x0092) 0.22% busy * ch 24 (0x0028) 0.06% busy * ch 25 (0x0063) 0.15% busy * ch 26 (0x058c) 2.16% busy * Done * @endcode * @par * Get the current channel monitor state and channel occupancy. * `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` is required. */ else if (aArgs[0] == "monitor") { if (aArgs[1].IsEmpty()) { OutputLine("enabled: %d", otChannelMonitorIsEnabled(GetInstancePtr())); if (otChannelMonitorIsEnabled(GetInstancePtr())) { uint32_t channelMask = otLinkGetSupportedChannelMask(GetInstancePtr()); uint8_t channelNum = BitSizeOf(channelMask); OutputLine("interval: %lu", ToUlong(otChannelMonitorGetSampleInterval(GetInstancePtr()))); OutputLine("threshold: %d", otChannelMonitorGetRssiThreshold(GetInstancePtr())); OutputLine("window: %lu", ToUlong(otChannelMonitorGetSampleWindow(GetInstancePtr()))); OutputLine("count: %lu", ToUlong(otChannelMonitorGetSampleCount(GetInstancePtr()))); OutputLine("occupancies:"); for (uint8_t channel = 0; channel < channelNum; channel++) { uint16_t occupancy; PercentageStringBuffer stringBuffer; if (!((1UL << channel) & channelMask)) { continue; } occupancy = otChannelMonitorGetChannelOccupancy(GetInstancePtr(), channel); OutputLine("ch %u (0x%04x) %6s%% busy", channel, occupancy, PercentageToString(occupancy, stringBuffer)); } OutputNewLine(); } } /** * @cli channel monitor start * @code * channel monitor start * channel monitor start * Done * @endcode * @par * Start the channel monitor. * OT CLI sends a boolean value of `true` to #otChannelMonitorSetEnabled. * `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` is required. * @sa otChannelMonitorSetEnabled */ else if (aArgs[1] == "start") { error = otChannelMonitorSetEnabled(GetInstancePtr(), true); } /** * @cli channel monitor stop * @code * channel monitor stop * channel monitor stop * Done * @endcode * @par * Stop the channel monitor. * OT CLI sends a boolean value of `false` to #otChannelMonitorSetEnabled. * `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` is required. * @sa otChannelMonitorSetEnabled */ else if (aArgs[1] == "stop") { error = otChannelMonitorSetEnabled(GetInstancePtr(), false); } else { ExitNow(error = OT_ERROR_INVALID_ARGS); } } #endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && \ (OPENTHREAD_FTD || \ (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)) else if (aArgs[0] == "manager") { /** * @cli channel manager * @code * channel manager * channel: 11 * auto: 1 * delay: 120 * interval: 10800 * supported: { 11-26} * favored: { 11-26} * Done * @endcode * @par * Get the channel manager state. * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE` is required. * @sa otChannelManagerGetRequestedChannel */ if (aArgs[1].IsEmpty()) { OutputLine("channel: %u", otChannelManagerGetRequestedChannel(GetInstancePtr())); #if OPENTHREAD_FTD OutputLine("auto: %d", otChannelManagerGetAutoChannelSelectionEnabled(GetInstancePtr())); #endif #if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) OutputLine("autocsl: %u", otChannelManagerGetAutoCslChannelSelectionEnabled(GetInstancePtr())); #endif #if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && \ OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) if (otChannelManagerGetAutoChannelSelectionEnabled(GetInstancePtr()) || otChannelManagerGetAutoCslChannelSelectionEnabled(GetInstancePtr())) #elif OPENTHREAD_FTD if (otChannelManagerGetAutoChannelSelectionEnabled(GetInstancePtr())) #elif (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) if (otChannelManagerGetAutoCslChannelSelectionEnabled(GetInstancePtr())) #endif { Mac::ChannelMask supportedMask(otChannelManagerGetSupportedChannels(GetInstancePtr())); Mac::ChannelMask favoredMask(otChannelManagerGetFavoredChannels(GetInstancePtr())); #if OPENTHREAD_FTD OutputLine("delay: %u", otChannelManagerGetDelay(GetInstancePtr())); #endif OutputLine("interval: %lu", ToUlong(otChannelManagerGetAutoChannelSelectionInterval(GetInstancePtr()))); OutputLine("cca threshold: 0x%04x", otChannelManagerGetCcaFailureRateThreshold(GetInstancePtr())); OutputLine("supported: %s", supportedMask.ToString().AsCString()); OutputLine("favored: %s", favoredMask.ToString().AsCString()); } } #if OPENTHREAD_FTD /** * @cli channel manager change * @code * channel manager change 11 * channel manager change 11 * Done * @endcode * @cparam channel manager change @ca{channel-num} * @par * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` is required. * @par api_copy * #otChannelManagerRequestChannelChange */ else if (aArgs[1] == "change") { error = ProcessSet(aArgs + 2, otChannelManagerRequestChannelChange); } #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE /** * @cli channel manager select * @code * channel manager select 1 * channel manager select 1 * Done * @endcode * @cparam channel manager select @ca{skip-quality-check} * Use a `1` or `0` for the boolean `skip-quality-check`. * @par * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` * are required. * @par api_copy * #otChannelManagerRequestChannelSelect */ else if (aArgs[1] == "select") { bool enable; SuccessOrExit(error = aArgs[2].ParseAsBool(enable)); error = otChannelManagerRequestChannelSelect(GetInstancePtr(), enable); } #endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE /** * @cli channel manager auto * @code * channel manager auto 1 * channel manager auto 1 * Done * @endcode * @cparam channel manager auto @ca{enable} * `1` is a boolean to `enable`. * @par * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` * are required. * @par api_copy * #otChannelManagerSetAutoChannelSelectionEnabled */ else if (aArgs[1] == "auto") { bool enable; SuccessOrExit(error = aArgs[2].ParseAsBool(enable)); otChannelManagerSetAutoChannelSelectionEnabled(GetInstancePtr(), enable); } #endif // OPENTHREAD_FTD #if (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) /** * @cli channel manager autocsl * @code * channel manager autocsl 1 * Done * @endcode * @cparam channel manager autocsl @ca{enable} * `1` is a boolean to `enable`. * @par * Enables or disables the auto channel selection functionality for a CSL channel. * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` * are required. * @sa otChannelManagerSetAutoCslChannelSelectionEnabled */ else if (aArgs[1] == "autocsl") { bool enable; SuccessOrExit(error = aArgs[2].ParseAsBool(enable)); otChannelManagerSetAutoCslChannelSelectionEnabled(GetInstancePtr(), enable); } #endif // (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE) #if OPENTHREAD_FTD /** * @cli channel manager delay * @code * channel manager delay 120 * channel manager delay 120 * Done * @endcode * @cparam channel manager delay @ca{delay-seconds} * @par * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` are required. * @par api_copy * #otChannelManagerSetDelay */ else if (aArgs[1] == "delay") { error = ProcessGetSet(aArgs + 2, otChannelManagerGetDelay, otChannelManagerSetDelay); } #endif /** * @cli channel manager interval * @code * channel manager interval 10800 * channel manager interval 10800 * Done * @endcode * @cparam channel manager interval @ca{interval-seconds} * @par * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` * are required. * @par api_copy * #otChannelManagerSetAutoChannelSelectionInterval */ else if (aArgs[1] == "interval") { error = ProcessSet(aArgs + 2, otChannelManagerSetAutoChannelSelectionInterval); } /** * @cli channel manager supported * @code * channel manager supported 0x7fffc00 * channel manager supported 0x7fffc00 * Done * @endcode * @cparam channel manager supported @ca{mask} * @par * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` * are required. * @par api_copy * #otChannelManagerSetSupportedChannels */ else if (aArgs[1] == "supported") { error = ProcessSet(aArgs + 2, otChannelManagerSetSupportedChannels); } /** * @cli channel manager favored * @code * channel manager favored 0x7fffc00 * channel manager favored 0x7fffc00 * Done * @endcode * @cparam channel manager favored @ca{mask} * @par * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` * are required. * @par api_copy * #otChannelManagerSetFavoredChannels */ else if (aArgs[1] == "favored") { error = ProcessSet(aArgs + 2, otChannelManagerSetFavoredChannels); } /** * @cli channel manager threshold * @code * channel manager threshold 0xffff * channel manager threshold 0xffff * Done * @endcode * @cparam channel manager threshold @ca{threshold-percent} * Use a hex value for `threshold-percent`. `0` maps to 0% and `0xffff` maps to 100%. * @par * `OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE` or `OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && * OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE`, and `OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE` * are required. * @par api_copy * #otChannelManagerSetCcaFailureRateThreshold */ else if (aArgs[1] == "threshold") { error = ProcessSet(aArgs + 2, otChannelManagerSetCcaFailureRateThreshold); } else { ExitNow(error = OT_ERROR_INVALID_ARGS); } } #endif // OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD else { ExitNow(error = ProcessGetSet(aArgs, otLinkGetChannel, otLinkSetChannel)); } exit: return error; } #if OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; otChildInfo childInfo; uint16_t childId; bool isTable; otLinkModeConfig linkMode; char linkModeString[kLinkModeStringSize]; isTable = (aArgs[0] == "table"); if (isTable || (aArgs[0] == "list")) { uint16_t maxChildren; /** * @cli child table * @code * child table * | ID | RLOC16 | Timeout | Age | LQ In | C_VN |R|D|N|Ver|CSL|QMsgCnt| Extended MAC | * +-----+--------+------------+------------+-------+------+-+-+-+---+---+-------+------------------+ * | 1 | 0xc801 | 240 | 24 | 3 | 131 |1|0|0| 3| 0 | 0 | 4ecede68435358ac | * | 2 | 0xc802 | 240 | 2 | 3 | 131 |0|0|0| 3| 1 | 0 | a672a601d2ce37d8 | * Done * @endcode * @par * Prints a table of the attached children. * @sa otThreadGetChildInfoByIndex */ if (isTable) { static const char *const kChildTableTitles[] = { "ID", "RLOC16", "Timeout", "Age", "LQ In", "C_VN", "R", "D", "N", "Ver", "CSL", "QMsgCnt", "Suprvsn", "Extended MAC", }; static const uint8_t kChildTableColumnWidths[] = { 5, 8, 12, 12, 7, 6, 1, 1, 1, 3, 3, 7, 7, 18, }; OutputTableHeader(kChildTableTitles, kChildTableColumnWidths); } maxChildren = otThreadGetMaxAllowedChildren(GetInstancePtr()); for (uint16_t i = 0; i < maxChildren; i++) { if ((otThreadGetChildInfoByIndex(GetInstancePtr(), i, &childInfo) != OT_ERROR_NONE) || childInfo.mIsStateRestoring) { continue; } if (isTable) { OutputFormat("| %3u ", childInfo.mChildId); OutputFormat("| 0x%04x ", childInfo.mRloc16); OutputFormat("| %10lu ", ToUlong(childInfo.mTimeout)); OutputFormat("| %10lu ", ToUlong(childInfo.mAge)); OutputFormat("| %5u ", childInfo.mLinkQualityIn); OutputFormat("| %4u ", childInfo.mNetworkDataVersion); OutputFormat("|%1d", childInfo.mRxOnWhenIdle); OutputFormat("|%1d", childInfo.mFullThreadDevice); OutputFormat("|%1d", childInfo.mFullNetworkData); OutputFormat("|%3u", childInfo.mVersion); OutputFormat("| %1d ", childInfo.mIsCslSynced); OutputFormat("| %5u ", childInfo.mQueuedMessageCnt); OutputFormat("| %5u ", childInfo.mSupervisionInterval); OutputFormat("| "); OutputExtAddress(childInfo.mExtAddress); OutputLine(" |"); } /** * @cli child list * @code * child list * 1 2 3 6 7 8 * Done * @endcode * @par * Returns a list of attached Child IDs. * @sa otThreadGetChildInfoByIndex */ else { OutputFormat("%u ", childInfo.mChildId); } } OutputNewLine(); ExitNow(); } SuccessOrExit(error = aArgs[0].ParseAsUint16(childId)); SuccessOrExit(error = otThreadGetChildInfoById(GetInstancePtr(), childId, &childInfo)); /** * @cli child (id) * @code * child 1 * Child ID: 1 * Rloc: 9c01 * Ext Addr: e2b3540590b0fd87 * Mode: rn * CSL Synchronized: 1 * Net Data: 184 * Timeout: 100 * Age: 0 * Link Quality In: 3 * RSSI: -20 * Done * @endcode * @cparam child @ca{child-id} * @par api_copy * #otThreadGetChildInfoById */ OutputLine("Child ID: %u", childInfo.mChildId); OutputLine("Rloc: %04x", childInfo.mRloc16); OutputFormat("Ext Addr: "); OutputExtAddressLine(childInfo.mExtAddress); linkMode.mRxOnWhenIdle = childInfo.mRxOnWhenIdle; linkMode.mDeviceType = childInfo.mFullThreadDevice; linkMode.mNetworkData = childInfo.mFullThreadDevice; OutputLine("Mode: %s", LinkModeToString(linkMode, linkModeString)); OutputLine("CSL Synchronized: %d ", childInfo.mIsCslSynced); OutputLine("Net Data: %u", childInfo.mNetworkDataVersion); OutputLine("Timeout: %lu", ToUlong(childInfo.mTimeout)); OutputLine("Age: %lu", ToUlong(childInfo.mAge)); OutputLine("Link Quality In: %u", childInfo.mLinkQualityIn); OutputLine("RSSI: %d", childInfo.mAverageRssi); OutputLine("Supervision Interval: %d", childInfo.mSupervisionInterval); exit: return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli childip * @code * childip * 3401: fdde:ad00:beef:0:3037:3e03:8c5f:bc0c * Done * @endcode * @par * Gets a list of IP addresses stored for MTD children. * @sa otThreadGetChildNextIp6Address */ if (aArgs[0].IsEmpty()) { uint16_t maxChildren = otThreadGetMaxAllowedChildren(GetInstancePtr()); for (uint16_t childIndex = 0; childIndex < maxChildren; childIndex++) { otChildIp6AddressIterator iterator = OT_CHILD_IP6_ADDRESS_ITERATOR_INIT; otIp6Address ip6Address; otChildInfo childInfo; if ((otThreadGetChildInfoByIndex(GetInstancePtr(), childIndex, &childInfo) != OT_ERROR_NONE) || childInfo.mIsStateRestoring) { continue; } iterator = OT_CHILD_IP6_ADDRESS_ITERATOR_INIT; while (otThreadGetChildNextIp6Address(GetInstancePtr(), childIndex, &iterator, &ip6Address) == OT_ERROR_NONE) { OutputFormat("%04x: ", childInfo.mRloc16); OutputIp6AddressLine(ip6Address); } } } /** * @cli childip max * @code * childip max * 4 * Done * @endcode * @par api_copy * #otThreadGetMaxChildIpAddresses */ else if (aArgs[0] == "max") { #if !OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE error = ProcessGet(aArgs + 1, otThreadGetMaxChildIpAddresses); #else /** * @cli childip max (set) * @code * childip max 2 * Done * @endcode * @cparam childip max @ca{count} * @par api_copy * #otThreadSetMaxChildIpAddresses */ error = ProcessGetSet(aArgs + 1, otThreadGetMaxChildIpAddresses, otThreadSetMaxChildIpAddresses); #endif } else { error = OT_ERROR_INVALID_COMMAND; } return error; } /** * @cli childmax * @code * childmax * 5 * Done * @endcode * @par api_copy * #otThreadGetMaxAllowedChildren */ template <> otError Interpreter::Process(Arg aArgs[]) { /** * @cli childmax (set) * @code * childmax 2 * Done * @endcode * @cparam childmax @ca{count} * @par api_copy * #otThreadSetMaxAllowedChildren */ return ProcessGetSet(aArgs, otThreadGetMaxAllowedChildren, otThreadSetMaxAllowedChildren); } #endif // OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_INVALID_ARGS; /** * @cli childsupervision checktimeout * @code * childsupervision checktimeout * 30 * Done * @endcode * @par api_copy * #otChildSupervisionGetCheckTimeout */ if (aArgs[0] == "checktimeout") { /** @cli childsupervision checktimeout (set) * @code * childsupervision checktimeout 30 * Done * @endcode * @cparam childsupervision checktimeout @ca{timeout-seconds} * @par api_copy * #otChildSupervisionSetCheckTimeout */ error = ProcessGetSet(aArgs + 1, otChildSupervisionGetCheckTimeout, otChildSupervisionSetCheckTimeout); } /** * @cli childsupervision interval * @code * childsupervision interval * 30 * Done * @endcode * @par api_copy * #otChildSupervisionGetInterval */ else if (aArgs[0] == "interval") { /** * @cli childsupervision interval (set) * @code * childsupervision interval 30 * Done * @endcode * @cparam childsupervision interval @ca{interval-seconds} * @par api_copy * #otChildSupervisionSetInterval */ error = ProcessGetSet(aArgs + 1, otChildSupervisionGetInterval, otChildSupervisionSetInterval); } else if (aArgs[0] == "failcounter") { if (aArgs[1].IsEmpty()) { OutputLine("%u", otChildSupervisionGetCheckFailureCounter(GetInstancePtr())); error = OT_ERROR_NONE; } else if (aArgs[1] == "reset") { otChildSupervisionResetCheckFailureCounter(GetInstancePtr()); error = OT_ERROR_NONE; } } return error; } /** @cli childtimeout * @code * childtimeout * 300 * Done * @endcode * @par api_copy * #otThreadGetChildTimeout */ template <> otError Interpreter::Process(Arg aArgs[]) { /** @cli childtimeout (set) * @code * childtimeout 300 * Done * @endcode * @cparam childtimeout @ca{timeout-seconds} * @par api_copy * #otThreadSetChildTimeout */ return ProcessGetSet(aArgs, otThreadGetChildTimeout, otThreadSetChildTimeout); } #if OPENTHREAD_CONFIG_COAP_API_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mCoap.Process(aArgs); } #endif #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mCoapSecure.Process(aArgs); } #endif #if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; if (ProcessEnableDisable(aArgs, otPlatRadioIsCoexEnabled, otPlatRadioSetCoexEnabled) == OT_ERROR_NONE) { } else if (aArgs[0] == "metrics") { struct RadioCoexMetricName { const uint32_t otRadioCoexMetrics::*mValuePtr; const char *mName; }; static const RadioCoexMetricName kTxMetricNames[] = { {&otRadioCoexMetrics::mNumTxRequest, "Request"}, {&otRadioCoexMetrics::mNumTxGrantImmediate, "Grant Immediate"}, {&otRadioCoexMetrics::mNumTxGrantWait, "Grant Wait"}, {&otRadioCoexMetrics::mNumTxGrantWaitActivated, "Grant Wait Activated"}, {&otRadioCoexMetrics::mNumTxGrantWaitTimeout, "Grant Wait Timeout"}, {&otRadioCoexMetrics::mNumTxGrantDeactivatedDuringRequest, "Grant Deactivated During Request"}, {&otRadioCoexMetrics::mNumTxDelayedGrant, "Delayed Grant"}, {&otRadioCoexMetrics::mAvgTxRequestToGrantTime, "Average Request To Grant Time"}, }; static const RadioCoexMetricName kRxMetricNames[] = { {&otRadioCoexMetrics::mNumRxRequest, "Request"}, {&otRadioCoexMetrics::mNumRxGrantImmediate, "Grant Immediate"}, {&otRadioCoexMetrics::mNumRxGrantWait, "Grant Wait"}, {&otRadioCoexMetrics::mNumRxGrantWaitActivated, "Grant Wait Activated"}, {&otRadioCoexMetrics::mNumRxGrantWaitTimeout, "Grant Wait Timeout"}, {&otRadioCoexMetrics::mNumRxGrantDeactivatedDuringRequest, "Grant Deactivated During Request"}, {&otRadioCoexMetrics::mNumRxDelayedGrant, "Delayed Grant"}, {&otRadioCoexMetrics::mAvgRxRequestToGrantTime, "Average Request To Grant Time"}, {&otRadioCoexMetrics::mNumRxGrantNone, "Grant None"}, }; otRadioCoexMetrics metrics; SuccessOrExit(error = otPlatRadioGetCoexMetrics(GetInstancePtr(), &metrics)); OutputLine("Stopped: %s", metrics.mStopped ? "true" : "false"); OutputLine("Grant Glitch: %lu", ToUlong(metrics.mNumGrantGlitch)); OutputLine("Transmit metrics"); for (const RadioCoexMetricName &metric : kTxMetricNames) { OutputLine(kIndentSize, "%s: %lu", metric.mName, ToUlong(metrics.*metric.mValuePtr)); } OutputLine("Receive metrics"); for (const RadioCoexMetricName &metric : kRxMetricNames) { OutputLine(kIndentSize, "%s: %lu", metric.mName, ToUlong(metrics.*metric.mValuePtr)); } } else { error = OT_ERROR_INVALID_ARGS; } exit: return error; } #endif // OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE #if OPENTHREAD_FTD /** * @cli contextreusedelay (get,set) * @code * contextreusedelay * 11 * Done * @endcode * @code * contextreusedelay 11 * Done * @endcode * @cparam contextreusedelay @ca{delay} * Use the optional `delay` argument to set the `CONTEXT_ID_REUSE_DELAY`. * @par * Gets or sets the `CONTEXT_ID_REUSE_DELAY` value. * @sa otThreadGetContextIdReuseDelay * @sa otThreadSetContextIdReuseDelay */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessGetSet(aArgs, otThreadGetContextIdReuseDelay, otThreadSetContextIdReuseDelay); } #endif #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE void Interpreter::OutputBorderRouterCounters(void) { struct BrCounterName { const otPacketsAndBytes otBorderRoutingCounters::*mPacketsAndBytes; const char *mName; }; static const BrCounterName kCounterNames[] = { {&otBorderRoutingCounters::mInboundUnicast, "Inbound Unicast"}, {&otBorderRoutingCounters::mInboundMulticast, "Inbound Multicast"}, {&otBorderRoutingCounters::mOutboundUnicast, "Outbound Unicast"}, {&otBorderRoutingCounters::mOutboundMulticast, "Outbound Multicast"}, }; const otBorderRoutingCounters *brCounters = otIp6GetBorderRoutingCounters(GetInstancePtr()); Uint64StringBuffer uint64StringBuffer; for (const BrCounterName &counter : kCounterNames) { OutputFormat("%s:", counter.mName); OutputFormat(" Packets %s", Uint64ToString((brCounters->*counter.mPacketsAndBytes).mPackets, uint64StringBuffer)); OutputLine(" Bytes %s", Uint64ToString((brCounters->*counter.mPacketsAndBytes).mBytes, uint64StringBuffer)); } OutputLine("RA Rx: %lu", ToUlong(brCounters->mRaRx)); OutputLine("RA TxSuccess: %lu", ToUlong(brCounters->mRaTxSuccess)); OutputLine("RA TxFailed: %lu", ToUlong(brCounters->mRaTxFailure)); OutputLine("RS Rx: %lu", ToUlong(brCounters->mRsRx)); OutputLine("RS TxSuccess: %lu", ToUlong(brCounters->mRsTxSuccess)); OutputLine("RS TxFailed: %lu", ToUlong(brCounters->mRsTxFailure)); } #endif // OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli counters * @code * counters * ip * mac * mle * Done * @endcode * @par * Gets the supported counter names. */ if (aArgs[0].IsEmpty()) { #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE OutputLine("br"); #endif OutputLine("ip"); OutputLine("mac"); OutputLine("mle"); } #if OPENTHREAD_CONFIG_IP6_BR_COUNTERS_ENABLE /** * @cli counters br * @code * counters br * Inbound Unicast: Packets 4 Bytes 320 * Inbound Multicast: Packets 0 Bytes 0 * Outbound Unicast: Packets 2 Bytes 160 * Outbound Multicast: Packets 0 Bytes 0 * RA Rx: 4 * RA TxSuccess: 2 * RA TxFailed: 0 * RS Rx: 0 * RS TxSuccess: 2 * RS TxFailed: 0 * Done * @endcode * @par api_copy * #otIp6GetBorderRoutingCounters */ else if (aArgs[0] == "br") { if (aArgs[1].IsEmpty()) { OutputBorderRouterCounters(); } /** * @cli counters br reset * @code * counters br reset * Done * @endcode * @par api_copy * #otIp6ResetBorderRoutingCounters */ else if ((aArgs[1] == "reset") && aArgs[2].IsEmpty()) { otIp6ResetBorderRoutingCounters(GetInstancePtr()); } else { error = OT_ERROR_INVALID_ARGS; } } #endif /** * @cli counters (mac) * @code * counters mac * TxTotal: 10 * TxUnicast: 3 * TxBroadcast: 7 * TxAckRequested: 3 * TxAcked: 3 * TxNoAckRequested: 7 * TxData: 10 * TxDataPoll: 0 * TxBeacon: 0 * TxBeaconRequest: 0 * TxOther: 0 * TxRetry: 0 * TxErrCca: 0 * TxErrBusyChannel: 0 * RxTotal: 2 * RxUnicast: 1 * RxBroadcast: 1 * RxData: 2 * RxDataPoll: 0 * RxBeacon: 0 * RxBeaconRequest: 0 * RxOther: 0 * RxAddressFiltered: 0 * RxDestAddrFiltered: 0 * RxDuplicated: 0 * RxErrNoFrame: 0 * RxErrNoUnknownNeighbor: 0 * RxErrInvalidSrcAddr: 0 * RxErrSec: 0 * RxErrFcs: 0 * RxErrOther: 0 * Done * @endcode * @cparam counters @ca{mac} * @par api_copy * #otLinkGetCounters */ else if (aArgs[0] == "mac") { if (aArgs[1].IsEmpty()) { struct MacCounterName { const uint32_t otMacCounters::*mValuePtr; const char *mName; }; static const MacCounterName kTxCounterNames[] = { {&otMacCounters::mTxUnicast, "TxUnicast"}, {&otMacCounters::mTxBroadcast, "TxBroadcast"}, {&otMacCounters::mTxAckRequested, "TxAckRequested"}, {&otMacCounters::mTxAcked, "TxAcked"}, {&otMacCounters::mTxNoAckRequested, "TxNoAckRequested"}, {&otMacCounters::mTxData, "TxData"}, {&otMacCounters::mTxDataPoll, "TxDataPoll"}, {&otMacCounters::mTxBeacon, "TxBeacon"}, {&otMacCounters::mTxBeaconRequest, "TxBeaconRequest"}, {&otMacCounters::mTxOther, "TxOther"}, {&otMacCounters::mTxRetry, "TxRetry"}, {&otMacCounters::mTxErrCca, "TxErrCca"}, {&otMacCounters::mTxErrBusyChannel, "TxErrBusyChannel"}, {&otMacCounters::mTxErrAbort, "TxErrAbort"}, {&otMacCounters::mTxDirectMaxRetryExpiry, "TxDirectMaxRetryExpiry"}, {&otMacCounters::mTxIndirectMaxRetryExpiry, "TxIndirectMaxRetryExpiry"}, }; static const MacCounterName kRxCounterNames[] = { {&otMacCounters::mRxUnicast, "RxUnicast"}, {&otMacCounters::mRxBroadcast, "RxBroadcast"}, {&otMacCounters::mRxData, "RxData"}, {&otMacCounters::mRxDataPoll, "RxDataPoll"}, {&otMacCounters::mRxBeacon, "RxBeacon"}, {&otMacCounters::mRxBeaconRequest, "RxBeaconRequest"}, {&otMacCounters::mRxOther, "RxOther"}, {&otMacCounters::mRxAddressFiltered, "RxAddressFiltered"}, {&otMacCounters::mRxDestAddrFiltered, "RxDestAddrFiltered"}, {&otMacCounters::mRxDuplicated, "RxDuplicated"}, {&otMacCounters::mRxErrNoFrame, "RxErrNoFrame"}, {&otMacCounters::mRxErrUnknownNeighbor, "RxErrNoUnknownNeighbor"}, {&otMacCounters::mRxErrInvalidSrcAddr, "RxErrInvalidSrcAddr"}, {&otMacCounters::mRxErrSec, "RxErrSec"}, {&otMacCounters::mRxErrFcs, "RxErrFcs"}, {&otMacCounters::mRxErrOther, "RxErrOther"}, }; const otMacCounters *macCounters = otLinkGetCounters(GetInstancePtr()); OutputLine("TxTotal: %lu", ToUlong(macCounters->mTxTotal)); for (const MacCounterName &counter : kTxCounterNames) { OutputLine(kIndentSize, "%s: %lu", counter.mName, ToUlong(macCounters->*counter.mValuePtr)); } OutputLine("RxTotal: %lu", ToUlong(macCounters->mRxTotal)); for (const MacCounterName &counter : kRxCounterNames) { OutputLine(kIndentSize, "%s: %lu", counter.mName, ToUlong(macCounters->*counter.mValuePtr)); } } /** * @cli counters mac reset * @code * counters mac reset * Done * @endcode * @cparam counters @ca{mac} reset * @par api_copy * #otLinkResetCounters */ else if ((aArgs[1] == "reset") && aArgs[2].IsEmpty()) { otLinkResetCounters(GetInstancePtr()); } else { error = OT_ERROR_INVALID_ARGS; } } /** * @cli counters (mle) * @code * counters mle * Role Disabled: 0 * Role Detached: 1 * Role Child: 0 * Role Router: 0 * Role Leader: 1 * Attach Attempts: 1 * Partition Id Changes: 1 * Better Partition Attach Attempts: 0 * Parent Changes: 0 * Done * @endcode * @cparam counters @ca{mle} * @par api_copy * #otThreadGetMleCounters */ else if (aArgs[0] == "mle") { if (aArgs[1].IsEmpty()) { struct MleCounterName { const uint16_t otMleCounters::*mValuePtr; const char *mName; }; static const MleCounterName kCounterNames[] = { {&otMleCounters::mDisabledRole, "Role Disabled"}, {&otMleCounters::mDetachedRole, "Role Detached"}, {&otMleCounters::mChildRole, "Role Child"}, {&otMleCounters::mRouterRole, "Role Router"}, {&otMleCounters::mLeaderRole, "Role Leader"}, {&otMleCounters::mAttachAttempts, "Attach Attempts"}, {&otMleCounters::mPartitionIdChanges, "Partition Id Changes"}, {&otMleCounters::mBetterPartitionAttachAttempts, "Better Partition Attach Attempts"}, {&otMleCounters::mParentChanges, "Parent Changes"}, }; const otMleCounters *mleCounters = otThreadGetMleCounters(GetInstancePtr()); for (const MleCounterName &counter : kCounterNames) { OutputLine("%s: %u", counter.mName, mleCounters->*counter.mValuePtr); } #if OPENTHREAD_CONFIG_UPTIME_ENABLE { struct MleTimeCounterName { const uint64_t otMleCounters::*mValuePtr; const char *mName; }; static const MleTimeCounterName kTimeCounterNames[] = { {&otMleCounters::mDisabledTime, "Disabled"}, {&otMleCounters::mDetachedTime, "Detached"}, {&otMleCounters::mChildTime, "Child"}, {&otMleCounters::mRouterTime, "Router"}, {&otMleCounters::mLeaderTime, "Leader"}, }; for (const MleTimeCounterName &counter : kTimeCounterNames) { OutputFormat("Time %s Milli: ", counter.mName); OutputUint64Line(mleCounters->*counter.mValuePtr); } OutputFormat("Time Tracked Milli: "); OutputUint64Line(mleCounters->mTrackedTime); } #endif } /** * @cli counters mle reset * @code * counters mle reset * Done * @endcode * @cparam counters @ca{mle} reset * @par api_copy * #otThreadResetMleCounters */ else if ((aArgs[1] == "reset") && aArgs[2].IsEmpty()) { otThreadResetMleCounters(GetInstancePtr()); } else { error = OT_ERROR_INVALID_ARGS; } } /** * @cli counters ip * @code * counters ip * TxSuccess: 10 * TxFailed: 0 * RxSuccess: 5 * RxFailed: 0 * Done * @endcode * @cparam counters @ca{ip} * @par api_copy * #otThreadGetIp6Counters */ else if (aArgs[0] == "ip") { if (aArgs[1].IsEmpty()) { struct IpCounterName { const uint32_t otIpCounters::*mValuePtr; const char *mName; }; static const IpCounterName kCounterNames[] = { {&otIpCounters::mTxSuccess, "TxSuccess"}, {&otIpCounters::mTxFailure, "TxFailed"}, {&otIpCounters::mRxSuccess, "RxSuccess"}, {&otIpCounters::mRxFailure, "RxFailed"}, }; const otIpCounters *ipCounters = otThreadGetIp6Counters(GetInstancePtr()); for (const IpCounterName &counter : kCounterNames) { OutputLine("%s: %lu", counter.mName, ToUlong(ipCounters->*counter.mValuePtr)); } } /** * @cli counters ip reset * @code * counters ip reset * Done * @endcode * @cparam counters @ca{ip} reset * @par api_copy * #otThreadResetIp6Counters */ else if ((aArgs[1] == "reset") && aArgs[2].IsEmpty()) { otThreadResetIp6Counters(GetInstancePtr()); } else { error = OT_ERROR_INVALID_ARGS; } } else { error = OT_ERROR_INVALID_ARGS; } return error; } #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli csl * @code * csl * Channel: 11 * Period: 160000us * Timeout: 1000s * Done * @endcode * @par * Gets the CSL configuration. * @sa otLinkGetCslChannel * @sa otLinkGetCslPeriod * @sa otLinkGetCslPeriod * @sa otLinkGetCslTimeout */ if (aArgs[0].IsEmpty()) { OutputLine("channel: %u", otLinkGetCslChannel(GetInstancePtr())); OutputLine("period: %luus", ToUlong(otLinkGetCslPeriod(GetInstancePtr()))); OutputLine("timeout: %lus", ToUlong(otLinkGetCslTimeout(GetInstancePtr()))); } /** * @cli csl channel * @code * csl channel 20 * Done * @endcode * @cparam csl channel @ca{channel} * @par api_copy * #otLinkSetCslChannel */ else if (aArgs[0] == "channel") { error = ProcessSet(aArgs + 1, otLinkSetCslChannel); } /** * @cli csl period * @code * csl period 3000000 * Done * @endcode * @cparam csl period @ca{period} * @par api_copy * #otLinkSetCslPeriod */ else if (aArgs[0] == "period") { error = ProcessSet(aArgs + 1, otLinkSetCslPeriod); } /** * @cli csl timeout * @code * cls timeout 10 * Done * @endcode * @cparam csl timeout @ca{timeout} * @par api_copy * #otLinkSetCslTimeout */ else if (aArgs[0] == "timeout") { error = ProcessSet(aArgs + 1, otLinkSetCslTimeout); } else { error = OT_ERROR_INVALID_ARGS; } return error; } #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE #if OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli delaytimermin * @code * delaytimermin * 30 * Done * @endcode * @par * Get the minimal delay timer (in seconds). * @sa otDatasetGetDelayTimerMinimal */ if (aArgs[0].IsEmpty()) { OutputLine("%lu", ToUlong((otDatasetGetDelayTimerMinimal(GetInstancePtr()) / 1000))); } /** * @cli delaytimermin (set) * @code * delaytimermin 60 * Done * @endcode * @cparam delaytimermin @ca{delaytimermin} * @par * Sets the minimal delay timer (in seconds). * @sa otDatasetSetDelayTimerMinimal */ else if (aArgs[1].IsEmpty()) { uint32_t delay; SuccessOrExit(error = aArgs[0].ParseAsUint32(delay)); SuccessOrExit(error = otDatasetSetDelayTimerMinimal(GetInstancePtr(), static_cast(delay * 1000))); } else { error = OT_ERROR_INVALID_ARGS; } exit: return error; } #endif /** * @cli detach * @code * detach * Finished detaching * Done * @endcode * @par * Start the graceful detach process by first notifying other nodes (sending Address Release if acting as a router, or * setting Child Timeout value to zero on parent if acting as a child) and then stopping Thread protocol operation. * @sa otThreadDetachGracefully */ template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli detach async * @code * detach async * Done * @endcode * @par * Start the graceful detach process similar to the `detach` command without blocking and waiting for the callback * indicating that detach is finished. * @csa{detach} * @sa otThreadDetachGracefully */ if (aArgs[0] == "async") { SuccessOrExit(error = otThreadDetachGracefully(GetInstancePtr(), nullptr, nullptr)); } else { SuccessOrExit(error = otThreadDetachGracefully(GetInstancePtr(), HandleDetachGracefullyResult, this)); error = OT_ERROR_PENDING; } exit: return error; } void Interpreter::HandleDetachGracefullyResult(void *aContext) { static_cast(aContext)->HandleDetachGracefullyResult(); } void Interpreter::HandleDetachGracefullyResult(void) { OutputLine("Finished detaching"); OutputResult(OT_ERROR_NONE); } /** * @cli discover * @code * discover * | J | Network Name | Extended PAN | PAN | MAC Address | Ch | dBm | LQI | * +---+------------------+------------------+------+------------------+----+-----+-----+ * | 0 | OpenThread | dead00beef00cafe | ffff | f1d92a82c8d8fe43 | 11 | -20 | 0 | * Done * @endcode * @cparam discover [@ca{channel}] * `channel`: The channel to discover on. If no channel is provided, the discovery will cover all * valid channels. * @par * Perform an MLE Discovery operation. * @sa otThreadDiscover */ template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; uint32_t scanChannels = 0; #if OPENTHREAD_FTD /** * @cli discover reqcallback (enable,disable) * @code * discover reqcallback enable * Done * @endcode * @cparam discover reqcallback @ca{enable|disable} * @par api_copy * #otThreadSetDiscoveryRequestCallback */ if (aArgs[0] == "reqcallback") { bool enable; otThreadDiscoveryRequestCallback callback = nullptr; void *context = nullptr; SuccessOrExit(error = ParseEnableOrDisable(aArgs[1], enable)); if (enable) { callback = &Interpreter::HandleDiscoveryRequest; context = this; } otThreadSetDiscoveryRequestCallback(GetInstancePtr(), callback, context); ExitNow(); } #endif // OPENTHREAD_FTD if (!aArgs[0].IsEmpty()) { uint8_t channel; SuccessOrExit(error = aArgs[0].ParseAsUint8(channel)); VerifyOrExit(channel < BitSizeOf(scanChannels), error = OT_ERROR_INVALID_ARGS); scanChannels = 1 << channel; } SuccessOrExit(error = otThreadDiscover(GetInstancePtr(), scanChannels, OT_PANID_BROADCAST, false, false, &Interpreter::HandleActiveScanResult, this)); static const char *const kScanTableTitles[] = { "Network Name", "Extended PAN", "PAN", "MAC Address", "Ch", "dBm", "LQI", }; static const uint8_t kScanTableColumnWidths[] = { 18, 18, 6, 18, 4, 5, 5, }; OutputTableHeader(kScanTableTitles, kScanTableColumnWidths); error = OT_ERROR_PENDING; exit: return error; } #if OPENTHREAD_CLI_DNS_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mDns.Process(aArgs); } #endif #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE && OPENTHREAD_CONFIG_MULTICAST_DNS_PUBLIC_API_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mMdns.Process(aArgs); } #endif #if OPENTHREAD_FTD void Interpreter::OutputEidCacheEntry(const otCacheEntryInfo &aEntry) { static const char *const kStateStrings[] = { "cache", // (0) OT_CACHE_ENTRY_STATE_CACHED "snoop", // (1) OT_CACHE_ENTRY_STATE_SNOOPED "query", // (2) OT_CACHE_ENTRY_STATE_QUERY "retry", // (3) OT_CACHE_ENTRY_STATE_RETRY_QUERY }; static_assert(0 == OT_CACHE_ENTRY_STATE_CACHED, "OT_CACHE_ENTRY_STATE_CACHED value is incorrect"); static_assert(1 == OT_CACHE_ENTRY_STATE_SNOOPED, "OT_CACHE_ENTRY_STATE_SNOOPED value is incorrect"); static_assert(2 == OT_CACHE_ENTRY_STATE_QUERY, "OT_CACHE_ENTRY_STATE_QUERY value is incorrect"); static_assert(3 == OT_CACHE_ENTRY_STATE_RETRY_QUERY, "OT_CACHE_ENTRY_STATE_RETRY_QUERY value is incorrect"); OutputIp6Address(aEntry.mTarget); OutputFormat(" %04x", aEntry.mRloc16); OutputFormat(" %s", Stringify(aEntry.mState, kStateStrings)); OutputFormat(" canEvict=%d", aEntry.mCanEvict); if (aEntry.mState == OT_CACHE_ENTRY_STATE_CACHED) { if (aEntry.mValidLastTrans) { OutputFormat(" transTime=%lu eid=", ToUlong(aEntry.mLastTransTime)); OutputIp6Address(aEntry.mMeshLocalEid); } } else { OutputFormat(" timeout=%u", aEntry.mTimeout); } if (aEntry.mState == OT_CACHE_ENTRY_STATE_RETRY_QUERY) { OutputFormat(" retryDelay=%u rampDown=%d", aEntry.mRetryDelay, aEntry.mRampDown); } OutputNewLine(); } /** * @cli eidcache * @code * eidcache * fd49:caf4:a29f:dc0e:97fc:69dd:3c16:df7d 2000 cache canEvict=1 transTime=0 eid=fd49:caf4:a29f:dc0e:97fc:69dd:3c16:df7d * fd49:caf4:a29f:dc0e:97fc:69dd:3c16:df7f fffe retry canEvict=1 timeout=10 retryDelay=30 * Done * @endcode * @par * Returns the EID-to-RLOC cache entries. * @sa otThreadGetNextCacheEntry */ template <> otError Interpreter::Process(Arg aArgs[]) { OT_UNUSED_VARIABLE(aArgs); otCacheEntryIterator iterator; otCacheEntryInfo entry; ClearAllBytes(iterator); while (true) { SuccessOrExit(otThreadGetNextCacheEntry(GetInstancePtr(), &entry, &iterator)); OutputEidCacheEntry(entry); } exit: return OT_ERROR_NONE; } #endif /** * @cli eui64 * @code * eui64 * 0615aae900124b00 * Done * @endcode * @par api_copy * #otPlatRadioGetIeeeEui64 */ template <> otError Interpreter::Process(Arg aArgs[]) { OT_UNUSED_VARIABLE(aArgs); otError error = OT_ERROR_NONE; otExtAddress extAddress; VerifyOrExit(aArgs[0].IsEmpty(), error = OT_ERROR_INVALID_ARGS); otLinkGetFactoryAssignedIeeeEui64(GetInstancePtr(), &extAddress); OutputExtAddressLine(extAddress); exit: return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli extaddr * @code * extaddr * dead00beef00cafe * Done * @endcode * @par api_copy * #otLinkGetExtendedAddress */ if (aArgs[0].IsEmpty()) { OutputExtAddressLine(*otLinkGetExtendedAddress(GetInstancePtr())); } /** * @cli extaddr (set) * @code * extaddr dead00beef00cafe * dead00beef00cafe * Done * @endcode * @cparam extaddr @ca{extaddr} * @par api_copy * #otLinkSetExtendedAddress */ else { otExtAddress extAddress; SuccessOrExit(error = aArgs[0].ParseAsHexString(extAddress.m8)); error = otLinkSetExtendedAddress(GetInstancePtr(), &extAddress); } exit: return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli log level * @code * log level * 1 * Done * @endcode * @par * Get the log level. * @sa otLoggingGetLevel */ if (aArgs[0] == "level") { if (aArgs[1].IsEmpty()) { OutputLine("%d", otLoggingGetLevel()); } else { #if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE uint8_t level; /** * @cli log level (set) * @code * log level 4 * Done * @endcode * @par api_copy * #otLoggingSetLevel * @cparam log level @ca{level} */ VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = aArgs[1].ParseAsUint8(level)); error = otLoggingSetLevel(static_cast(level)); #else error = OT_ERROR_INVALID_ARGS; #endif } } #if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_DEBUG_UART) && OPENTHREAD_POSIX /** * @cli log filename * @par * Specifies filename to capture `otPlatLog()` messages, useful when debugging * automated test scripts on Linux when logging disrupts the automated test scripts. * @par * Requires `OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_DEBUG_UART` * and `OPENTHREAD_POSIX`. * @par api_copy * #otPlatDebugUart_logfile * @cparam log filename @ca{filename} */ else if (aArgs[0] == "filename") { VerifyOrExit(!aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = otPlatDebugUart_logfile(aArgs[1].GetCString())); } #endif else { ExitNow(error = OT_ERROR_INVALID_ARGS); } exit: return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli extpanid * @code * extpanid * dead00beef00cafe * Done * @endcode * @par api_copy * #otThreadGetExtendedPanId */ if (aArgs[0].IsEmpty()) { OutputBytesLine(otThreadGetExtendedPanId(GetInstancePtr())->m8); } /** * @cli extpanid (set) * @code * extpanid dead00beef00cafe * Done * @endcode * @cparam extpanid @ca{extpanid} * @par * @note The current commissioning credential becomes stale after changing this value. * Use `pskc` to reset. * @par api_copy * #otThreadSetExtendedPanId */ else { otExtendedPanId extPanId; SuccessOrExit(error = aArgs[0].ParseAsHexString(extPanId.m8)); error = otThreadSetExtendedPanId(GetInstancePtr(), &extPanId); } exit: return error; } /** * @cli factoryreset * @code * factoryreset * @endcode * @par api_copy * #otInstanceFactoryReset */ template <> otError Interpreter::Process(Arg aArgs[]) { OT_UNUSED_VARIABLE(aArgs); otInstanceFactoryReset(GetInstancePtr()); return OT_ERROR_NONE; } #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_INVALID_COMMAND; /** * @cli fake (a,an) * @code * fake /a/an fdde:ad00:beef:0:0:ff:fe00:a800 fd00:7d03:7d03:7d03:55f2:bb6a:7a43:a03b 1111222233334444 * Done * @endcode * @cparam fake /a/an @ca{dst-ipaddr} @ca{target} @ca{meshLocalIid} * @par * Sends fake Thread messages. * @par * Available when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is enabled. * @sa otThreadSendAddressNotification */ if (aArgs[0] == "/a/an") { otIp6Address destination, target; otIp6InterfaceIdentifier mlIid; SuccessOrExit(error = aArgs[1].ParseAsIp6Address(destination)); SuccessOrExit(error = aArgs[2].ParseAsIp6Address(target)); SuccessOrExit(error = aArgs[3].ParseAsHexString(mlIid.mFields.m8)); otThreadSendAddressNotification(GetInstancePtr(), &destination, &target, &mlIid); } #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE else if (aArgs[0] == "/b/ba") { otIp6Address target; otIp6InterfaceIdentifier mlIid; uint32_t timeSinceLastTransaction; SuccessOrExit(error = aArgs[1].ParseAsIp6Address(target)); SuccessOrExit(error = aArgs[2].ParseAsHexString(mlIid.mFields.m8)); SuccessOrExit(error = aArgs[3].ParseAsUint32(timeSinceLastTransaction)); error = otThreadSendProactiveBackboneNotification(GetInstancePtr(), &target, &mlIid, timeSinceLastTransaction); } #endif exit: return error; } #endif template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli fem * @code * fem * LNA gain 11 dBm * Done * @endcode * @par * Gets external FEM parameters. * @sa otPlatRadioGetFemLnaGain */ if (aArgs[0].IsEmpty()) { int8_t lnaGain; SuccessOrExit(error = otPlatRadioGetFemLnaGain(GetInstancePtr(), &lnaGain)); OutputLine("LNA gain %d dBm", lnaGain); } /** * @cli fem lnagain (get) * @code * fem lnagain * 11 * Done * @endcode * @par api_copy * #otPlatRadioGetFemLnaGain */ else if (aArgs[0] == "lnagain") { if (aArgs[1].IsEmpty()) { int8_t lnaGain; SuccessOrExit(error = otPlatRadioGetFemLnaGain(GetInstancePtr(), &lnaGain)); OutputLine("%d", lnaGain); } /** * @cli fem lnagain (set) * @code * fem lnagain 8 * Done * @endcode * @par api_copy * #otPlatRadioSetFemLnaGain */ else { int8_t lnaGain; SuccessOrExit(error = aArgs[1].ParseAsInt8(lnaGain)); SuccessOrExit(error = otPlatRadioSetFemLnaGain(GetInstancePtr(), lnaGain)); } } else { error = OT_ERROR_INVALID_ARGS; } exit: return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli ifconfig * @code * ifconfig * down * Done * @endcode * @code * ifconfig * up * Done * @endcode * @par api_copy * #otIp6IsEnabled */ if (aArgs[0].IsEmpty()) { if (otIp6IsEnabled(GetInstancePtr())) { OutputLine("up"); } else { OutputLine("down"); } } /** * @cli ifconfig (up,down) * @code * ifconfig up * Done * @endcode * @code * ifconfig down * Done * @endcode * @cparam ifconfig @ca{up|down} * @par api_copy * #otIp6SetEnabled */ else if (aArgs[0] == "up") { SuccessOrExit(error = otIp6SetEnabled(GetInstancePtr(), true)); } else if (aArgs[0] == "down") { SuccessOrExit(error = otIp6SetEnabled(GetInstancePtr(), false)); } else { ExitNow(error = OT_ERROR_INVALID_ARGS); } exit: return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_INVALID_ARGS; /** * @cli instanceid * @code * instanceid * 468697314 * Done * @endcode * @par api_copy * #otInstanceGetId */ if (aArgs[0].IsEmpty()) { OutputLine("%lu", ToUlong(otInstanceGetId(GetInstancePtr()))); error = OT_ERROR_NONE; } return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; bool verbose = false; if (aArgs[0] == "-v") { aArgs++; verbose = true; } /** * @cli ipaddr * @code * ipaddr * fdde:ad00:beef:0:0:ff:fe00:0 * fdde:ad00:beef:0:558:f56b:d688:799 * fe80:0:0:0:f3d9:2a82:c8d8:fe43 * Done * @endcode * @code * ipaddr -v * fd5e:18fa:f4a5:b8:0:ff:fe00:fc00 origin:thread plen:64 preferred:0 valid:1 * fd5e:18fa:f4a5:b8:0:ff:fe00:dc00 origin:thread plen:64 preferred:0 valid:1 * fd5e:18fa:f4a5:b8:f8e:5d95:87a0:e82c origin:thread plen:64 preferred:0 valid:1 * fe80:0:0:0:4891:b191:e277:8826 origin:thread plen:64 preferred:1 valid:1 * Done * @endcode * @cparam ipaddr [@ca{-v}] * Use `-v` to get more verbose information about the address: * - `origin`: can be `thread`, `slaac`, `dhcp6`, `manual` and indicates the origin of the address * - `plen`: prefix length * - `preferred`: preferred flag (boolean) * - `valid`: valid flag (boolean) * @par api_copy * #otIp6GetUnicastAddresses */ if (aArgs[0].IsEmpty()) { const otNetifAddress *unicastAddrs = otIp6GetUnicastAddresses(GetInstancePtr()); for (const otNetifAddress *addr = unicastAddrs; addr; addr = addr->mNext) { OutputIp6Address(addr->mAddress); if (verbose) { OutputFormat(" origin:%s plen:%u preferred:%u valid:%u", AddressOriginToString(addr->mAddressOrigin), addr->mPrefixLength, addr->mPreferred, addr->mValid); } OutputNewLine(); } } /** * @cli ipaddr add * @code * ipaddr add 2001::dead:beef:cafe * Done * @endcode * @cparam ipaddr add @ca{aAddress} * @par api_copy * #otIp6AddUnicastAddress */ else if (aArgs[0] == "add") { otNetifAddress address; SuccessOrExit(error = aArgs[1].ParseAsIp6Address(address.mAddress)); address.mPrefixLength = 64; address.mPreferred = true; address.mValid = true; address.mAddressOrigin = OT_ADDRESS_ORIGIN_MANUAL; error = otIp6AddUnicastAddress(GetInstancePtr(), &address); } /** * @cli ipaddr del * @code * ipaddr del 2001::dead:beef:cafe * Done * @endcode * @cparam ipaddr del @ca{aAddress} * @par api_copy * #otIp6RemoveUnicastAddress */ else if (aArgs[0] == "del") { otIp6Address address; SuccessOrExit(error = aArgs[1].ParseAsIp6Address(address)); error = otIp6RemoveUnicastAddress(GetInstancePtr(), &address); } /** * @cli ipaddr linklocal * @code * ipaddr linklocal * fe80:0:0:0:f3d9:2a82:c8d8:fe43 * Done * @endcode * @par api_copy * #otThreadGetLinkLocalIp6Address */ else if (aArgs[0] == "linklocal") { OutputIp6AddressLine(*otThreadGetLinkLocalIp6Address(GetInstancePtr())); } /** * @cli ipaddr rloc * @code * ipaddr rloc * fdde:ad00:beef:0:0:ff:fe00:0 * Done * @endcode * @par api_copy * #otThreadGetRloc */ else if (aArgs[0] == "rloc") { OutputIp6AddressLine(*otThreadGetRloc(GetInstancePtr())); } /** * @cli ipaddr mleid * @code * ipaddr mleid * fdde:ad00:beef:0:558:f56b:d688:799 * Done * @endcode * @par api_copy * #otThreadGetMeshLocalEid */ else if (aArgs[0] == "mleid") { OutputIp6AddressLine(*otThreadGetMeshLocalEid(GetInstancePtr())); } else { error = OT_ERROR_INVALID_COMMAND; } exit: return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli ipmaddr * @code * ipmaddr * ff05:0:0:0:0:0:0:1 * ff33:40:fdde:ad00:beef:0:0:1 * ff32:40:fdde:ad00:beef:0:0:1 * Done * @endcode * @par api_copy * #otIp6GetMulticastAddresses */ if (aArgs[0].IsEmpty()) { for (const otNetifMulticastAddress *addr = otIp6GetMulticastAddresses(GetInstancePtr()); addr; addr = addr->mNext) { OutputIp6AddressLine(addr->mAddress); } } /** * @cli ipmaddr add * @code * ipmaddr add ff05::1 * Done * @endcode * @cparam ipmaddr add @ca{aAddress} * @par api_copy * #otIp6SubscribeMulticastAddress */ else if (aArgs[0] == "add") { otIp6Address address; aArgs++; do { SuccessOrExit(error = aArgs->ParseAsIp6Address(address)); SuccessOrExit(error = otIp6SubscribeMulticastAddress(GetInstancePtr(), &address)); } #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE while (!(++aArgs)->IsEmpty()); #else while (false); #endif } /** * @cli ipmaddr del * @code * ipmaddr del ff05::1 * Done * @endcode * @cparam ipmaddr del @ca{aAddress} * @par api_copy * #otIp6UnsubscribeMulticastAddress */ else if (aArgs[0] == "del") { otIp6Address address; SuccessOrExit(error = aArgs[1].ParseAsIp6Address(address)); error = otIp6UnsubscribeMulticastAddress(GetInstancePtr(), &address); } /** * @cli ipmaddr promiscuous * @code * ipmaddr promiscuous * Disabled * Done * @endcode * @par api_copy * #otIp6IsMulticastPromiscuousEnabled */ else if (aArgs[0] == "promiscuous") { /** * @cli ipmaddr promiscuous (enable,disable) * @code * ipmaddr promiscuous enable * Done * @endcode * @code * ipmaddr promiscuous disable * Done * @endcode * @cparam ipmaddr promiscuous @ca{enable|disable} * @par api_copy * #otIp6SetMulticastPromiscuousEnabled */ error = ProcessEnableDisable(aArgs + 1, otIp6IsMulticastPromiscuousEnabled, otIp6SetMulticastPromiscuousEnabled); } /** * @cli ipmaddr llatn * @code * ipmaddr llatn * ff32:40:fdde:ad00:beef:0:0:1 * Done * @endcode * @par api_copy * #otThreadGetLinkLocalAllThreadNodesMulticastAddress */ else if (aArgs[0] == "llatn") { OutputIp6AddressLine(*otThreadGetLinkLocalAllThreadNodesMulticastAddress(GetInstancePtr())); } /** * @cli ipmaddr rlatn * @code * ipmaddr rlatn * ff33:40:fdde:ad00:beef:0:0:1 * Done * @endcode * @par api_copy * #otThreadGetRealmLocalAllThreadNodesMulticastAddress */ else if (aArgs[0] == "rlatn") { OutputIp6AddressLine(*otThreadGetRealmLocalAllThreadNodesMulticastAddress(GetInstancePtr())); } else { error = OT_ERROR_INVALID_COMMAND; } exit: return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_INVALID_ARGS; /** * @cli keysequence counter * @code * keysequence counter * 10 * Done * @endcode * @par api_copy * #otThreadGetKeySequenceCounter */ if (aArgs[0] == "counter") { /** * @cli keysequence counter (set) * @code * keysequence counter 10 * Done * @endcode * @cparam keysequence counter @ca{counter} * @par api_copy * #otThreadSetKeySequenceCounter */ error = ProcessGetSet(aArgs + 1, otThreadGetKeySequenceCounter, otThreadSetKeySequenceCounter); } /** * @cli keysequence guardtime * @code * keysequence guardtime * 0 * Done * @endcode * @par api_copy * #otThreadGetKeySwitchGuardTime */ else if (aArgs[0] == "guardtime") { /** * @cli keysequence guardtime (set) * @code * keysequence guardtime 0 * Done * @endcode * @cparam keysequence guardtime @ca{guardtime-hours} * Use `0` to `Thread Key Switch` immediately if there's a key index match. * @par api_copy * #otThreadSetKeySwitchGuardTime */ error = ProcessGetSet(aArgs + 1, otThreadGetKeySwitchGuardTime, otThreadSetKeySwitchGuardTime); } return error; } /** * @cli leaderdata * @code * leaderdata * Partition ID: 1077744240 * Weighting: 64 * Data Version: 109 * Stable Data Version: 211 * Leader Router ID: 60 * Done * @endcode * @par * Gets the Thread Leader Data. * @sa otThreadGetLeaderData */ template <> otError Interpreter::Process(Arg aArgs[]) { OT_UNUSED_VARIABLE(aArgs); otError error; otLeaderData leaderData; SuccessOrExit(error = otThreadGetLeaderData(GetInstancePtr(), &leaderData)); OutputLine("Partition ID: %lu", ToUlong(leaderData.mPartitionId)); OutputLine("Weighting: %u", leaderData.mWeighting); OutputLine("Data Version: %u", leaderData.mDataVersion); OutputLine("Stable Data Version: %u", leaderData.mStableDataVersion); OutputLine("Leader Router ID: %u", leaderData.mLeaderRouterId); exit: return error; } #if OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_INVALID_COMMAND; /** * @cli partitionid * @code * partitionid * 4294967295 * Done * @endcode * @par * Get the Thread Network Partition ID. * @sa otThreadGetPartitionId */ if (aArgs[0].IsEmpty()) { OutputLine("%lu", ToUlong(otThreadGetPartitionId(GetInstancePtr()))); error = OT_ERROR_NONE; } #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE /** * @cli partitionid preferred (get,set) * @code * partitionid preferred * 4294967295 * Done * @endcode * @code * partitionid preferred 0xffffffff * Done * @endcode * @cparam partitionid preferred @ca{partitionid} * @sa otThreadGetPreferredLeaderPartitionId * @sa otThreadSetPreferredLeaderPartitionId * @par * `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is required. */ else if (aArgs[0] == "preferred") { error = ProcessGetSet(aArgs + 1, otThreadGetPreferredLeaderPartitionId, otThreadSetPreferredLeaderPartitionId); } #endif return error; } /** * @cli leaderweight * @code * leaderweight * 128 * Done * @endcode * @par api_copy * #otThreadGetLocalLeaderWeight */ template <> otError Interpreter::Process(Arg aArgs[]) { /** * @cli leaderweight (set) * @code * leaderweight 128 * Done * @endcode * @cparam leaderweight @ca{weight} * @par api_copy * #otThreadSetLocalLeaderWeight */ return ProcessGetSet(aArgs, otThreadGetLocalLeaderWeight, otThreadSetLocalLeaderWeight); } #if OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { static const char *const kPowerSupplyStrings[4] = { "battery", // (0) OT_POWER_SUPPLY_BATTERY "external", // (1) OT_POWER_SUPPLY_EXTERNAL "external-stable", // (2) OT_POWER_SUPPLY_EXTERNAL_STABLE "external-unstable", // (3) OT_POWER_SUPPLY_EXTERNAL_UNSTABLE }; static_assert(0 == OT_POWER_SUPPLY_BATTERY, "OT_POWER_SUPPLY_BATTERY value is incorrect"); static_assert(1 == OT_POWER_SUPPLY_EXTERNAL, "OT_POWER_SUPPLY_EXTERNAL value is incorrect"); static_assert(2 == OT_POWER_SUPPLY_EXTERNAL_STABLE, "OT_POWER_SUPPLY_EXTERNAL_STABLE value is incorrect"); static_assert(3 == OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, "OT_POWER_SUPPLY_EXTERNAL_UNSTABLE value is incorrect"); otError error = OT_ERROR_NONE; /** * @cli deviceprops * @code * deviceprops * PowerSupply : external * IsBorderRouter : yes * SupportsCcm : no * IsUnstable : no * WeightAdjustment : 0 * Done * @endcode * @par api_copy * #otThreadGetDeviceProperties */ if (aArgs[0].IsEmpty()) { const otDeviceProperties *props = otThreadGetDeviceProperties(GetInstancePtr()); OutputLine("PowerSupply : %s", Stringify(props->mPowerSupply, kPowerSupplyStrings)); OutputLine("IsBorderRouter : %s", props->mIsBorderRouter ? "yes" : "no"); OutputLine("SupportsCcm : %s", props->mSupportsCcm ? "yes" : "no"); OutputLine("IsUnstable : %s", props->mIsUnstable ? "yes" : "no"); OutputLine("WeightAdjustment : %d", props->mLeaderWeightAdjustment); } /** * @cli deviceprops (set) * @code * deviceprops battery 0 0 0 -5 * Done * @endcode * @code * deviceprops * PowerSupply : battery * IsBorderRouter : no * SupportsCcm : no * IsUnstable : no * WeightAdjustment : -5 * Done * @endcode * @cparam deviceprops @ca{powerSupply} @ca{isBr} @ca{supportsCcm} @ca{isUnstable} @ca{weightAdjustment} * `powerSupply`: should be 'battery', 'external', 'external-stable', 'external-unstable'. * @par * Sets the device properties. * @csa{leaderweight} * @csa{leaderweight (set)} * @sa #otThreadSetDeviceProperties */ else { otDeviceProperties props; bool value; uint8_t index; for (index = 0; index < OT_ARRAY_LENGTH(kPowerSupplyStrings); index++) { if (aArgs[0] == kPowerSupplyStrings[index]) { props.mPowerSupply = static_cast(index); break; } } VerifyOrExit(index < OT_ARRAY_LENGTH(kPowerSupplyStrings), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = aArgs[1].ParseAsBool(value)); props.mIsBorderRouter = value; SuccessOrExit(error = aArgs[2].ParseAsBool(value)); props.mSupportsCcm = value; SuccessOrExit(error = aArgs[3].ParseAsBool(value)); props.mIsUnstable = value; SuccessOrExit(error = aArgs[4].ParseAsInt8(props.mLeaderWeightAdjustment)); VerifyOrExit(aArgs[5].IsEmpty(), error = OT_ERROR_INVALID_ARGS); otThreadSetDeviceProperties(GetInstancePtr(), &props); } exit: return error; } #endif // OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE #endif // OPENTHREAD_FTD #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mLinkMetrics.Process(aArgs); } #if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli linkmetricsmgr (enable,disable) * @code * linkmetricmgr enable * Done * @endcode * @code * linkmetricmgr disable * Done * @endcode * @cparam linkmetricsmgr @ca{enable|disable} * @par api_copy * #otLinkMetricsManagerSetEnabled * */ if (ProcessEnableDisable(aArgs, otLinkMetricsManagerIsEnabled, otLinkMetricsManagerSetEnabled) == OT_ERROR_NONE) { } /** * @cli linkmetricsmgr show * @code * linkmetricsmgr show * ExtAddr:827aa7f7f63e1234, LinkMargin:80, Rssi:-20 * Done * @endcode * @par api_copy * #otLinkMetricsManagerGetMetricsValueByExtAddr * */ else if (aArgs[0] == "show") { otNeighborInfoIterator iterator = OT_NEIGHBOR_INFO_ITERATOR_INIT; otNeighborInfo neighborInfo; while (otThreadGetNextNeighborInfo(GetInstancePtr(), &iterator, &neighborInfo) == OT_ERROR_NONE) { otLinkMetricsValues linkMetricsValues; if (otLinkMetricsManagerGetMetricsValueByExtAddr(GetInstancePtr(), &neighborInfo.mExtAddress, &linkMetricsValues) != OT_ERROR_NONE) { continue; } OutputFormat("ExtAddr:"); OutputExtAddress(neighborInfo.mExtAddress); OutputLine(", LinkMargin:%u, Rssi:%d", linkMetricsValues.mLinkMarginValue, linkMetricsValues.mRssiValue); } } else { error = OT_ERROR_INVALID_COMMAND; } return error; } #endif // OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_INVALID_ARGS; otIp6Address anycastAddress; /** * @cli locate * @code * locate * Idle * Done * @endcode * @code * locate fdde:ad00:beef:0:0:ff:fe00:fc10 * @endcode * @code * locate * In Progress * Done * @endcode * @par * Gets the current state (`In Progress` or `Idle`) of anycast locator. * @par * Available when `OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE` is enabled. * @sa otThreadIsAnycastLocateInProgress */ if (aArgs[0].IsEmpty()) { OutputLine(otThreadIsAnycastLocateInProgress(GetInstancePtr()) ? "In Progress" : "Idle"); ExitNow(error = OT_ERROR_NONE); } /** * @cli locate (set) * @code * locate fdde:ad00:beef:0:0:ff:fe00:fc00 * fdde:ad00:beef:0:d9d3:9000:16b:d03b 0xc800 * Done * @endcode * @par * Locate the closest destination of an anycast address (i.e., find the * destination's mesh local EID and RLOC16). * @par * The closest destination is determined based on the current routing * table and path costs within the Thread mesh. * @par * Available when `OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE` is enabled. * @sa otThreadLocateAnycastDestination * @cparam locate @ca{anycastaddr} */ SuccessOrExit(error = aArgs[0].ParseAsIp6Address(anycastAddress)); SuccessOrExit(error = otThreadLocateAnycastDestination(GetInstancePtr(), &anycastAddress, HandleLocateResult, this)); SetCommandTimeout(kLocateTimeoutMsecs); mLocateInProgress = true; error = OT_ERROR_PENDING; exit: return error; } void Interpreter::HandleLocateResult(void *aContext, otError aError, const otIp6Address *aMeshLocalAddress, uint16_t aRloc16) { static_cast(aContext)->HandleLocateResult(aError, aMeshLocalAddress, aRloc16); } void Interpreter::HandleLocateResult(otError aError, const otIp6Address *aMeshLocalAddress, uint16_t aRloc16) { VerifyOrExit(mLocateInProgress); mLocateInProgress = false; if (aError == OT_ERROR_NONE) { OutputIp6Address(*aMeshLocalAddress); OutputLine(" 0x%04x", aRloc16); } OutputResult(aError); exit: return; } #endif // OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE #if OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; otPskc pskc; /** * @cli pskc * @code * pskc * 67c0c203aa0b042bfb5381c47aef4d9e * Done * @endcode * @par api_copy * #otThreadGetPskc */ if (aArgs[0].IsEmpty()) { otThreadGetPskc(GetInstancePtr(), &pskc); OutputBytesLine(pskc.m8); } else /** * @cli pskc (set) * @code * pskc 67c0c203aa0b042bfb5381c47aef4d9e * Done * @endcode * @cparam pskc @ca{key} * @par * Sets the pskc in hexadecimal format. */ { if (aArgs[1].IsEmpty()) { SuccessOrExit(error = aArgs[0].ParseAsHexString(pskc.m8)); } /** * @cli pskc -p * @code * pskc -p 123456 * Done * @endcode * @cparam pskc -p @ca{passphrase} * @par * Generates the pskc from the passphrase (UTF-8 encoded), together with the current network name and extended * PAN ID. */ else if (aArgs[0] == "-p") { SuccessOrExit(error = otDatasetGeneratePskc( aArgs[1].GetCString(), reinterpret_cast(otThreadGetNetworkName(GetInstancePtr())), otThreadGetExtendedPanId(GetInstancePtr()), &pskc)); } else { ExitNow(error = OT_ERROR_INVALID_ARGS); } error = otThreadSetPskc(GetInstancePtr(), &pskc); } exit: return error; } #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli pskcref * @code * pskcref * 0x80000000 * Done * @endcode * @par api_copy * #otThreadGetPskcRef */ if (aArgs[0].IsEmpty()) { OutputLine("0x%08lx", ToUlong(otThreadGetPskcRef(GetInstancePtr()))); } else { otPskcRef pskcRef; /** * @cli pskcref (set) * @code * pskc 0x20017 * Done * @endcode * @cparam pskc @ca{keyref} * @par api_copy * #otThreadSetPskcRef */ if (aArgs[1].IsEmpty()) { SuccessOrExit(error = aArgs[0].ParseAsUint32(pskcRef)); } else { ExitNow(error = OT_ERROR_INVALID_ARGS); } error = otThreadSetPskcRef(GetInstancePtr(), pskcRef); } exit: return error; } #endif #endif // OPENTHREAD_FTD #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE /** * @cli mleadvimax * @code * mleadvimax * 12000 * Done * @endcode * @par api_copy * #otThreadGetAdvertisementTrickleIntervalMax */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessGet(aArgs, otThreadGetAdvertisementTrickleIntervalMax); } #endif #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE /** * @cli mliid * @code * mliid 1122334455667788 * Done * @endcode * @par * It must be used before Thread stack is enabled. * @par * Only for testing/reference device. * @par api_copy * #otIp6SetMeshLocalIid * @cparam mliid @ca{iid} */ template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; otIp6InterfaceIdentifier iid; VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = aArgs[0].ParseAsHexString(iid.mFields.m8)); SuccessOrExit(error = otIp6SetMeshLocalIid(GetInstancePtr(), &iid)); exit: return error; } #endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE /** * @cli mlr reg * @code * mlr reg ff04::1 * status 0, 0 failed * Done * @endcode * @code * mlr reg ff04::1 ff04::2 ff02::1 * status 2, 1 failed * ff02:0:0:0:0:0:0:1 * Done * @endcode * @code * mlr reg ff04::1 ff04::2 1000 * status 0, 0 failed * Done * @endcode * @code * mlr reg ff04::1 ff04::2 0 * status 0, 0 failed * Done * @endcode * @par * Omit timeout to use the default MLR timeout on the Primary Backbone Router. * @par * Use timeout = 0 to deregister Multicast Listeners. * @par api_copy * #otIp6RegisterMulticastListeners * @cparam mlr reg @ca{ipaddr} [@ca{timeout}] */ template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_INVALID_COMMAND; if (aArgs[0] == "reg") { otIp6Address addresses[OT_IP6_MAX_MLR_ADDRESSES]; uint32_t timeout; bool hasTimeout = false; uint8_t numAddresses = 0; aArgs++; while (aArgs->ParseAsIp6Address(addresses[numAddresses]) == OT_ERROR_NONE) { aArgs++; numAddresses++; if (numAddresses == OT_ARRAY_LENGTH(addresses)) { break; } } if (aArgs->ParseAsUint32(timeout) == OT_ERROR_NONE) { aArgs++; hasTimeout = true; } VerifyOrExit(aArgs->IsEmpty() && (numAddresses > 0), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = otIp6RegisterMulticastListeners(GetInstancePtr(), addresses, numAddresses, hasTimeout ? &timeout : nullptr, Interpreter::HandleMlrRegResult, this)); error = OT_ERROR_PENDING; } exit: return error; } void Interpreter::HandleMlrRegResult(void *aContext, otError aError, uint8_t aMlrStatus, const otIp6Address *aFailedAddresses, uint8_t aFailedAddressNum) { static_cast(aContext)->HandleMlrRegResult(aError, aMlrStatus, aFailedAddresses, aFailedAddressNum); } void Interpreter::HandleMlrRegResult(otError aError, uint8_t aMlrStatus, const otIp6Address *aFailedAddresses, uint8_t aFailedAddressNum) { if (aError == OT_ERROR_NONE) { OutputLine("status %d, %d failed", aMlrStatus, aFailedAddressNum); for (uint8_t i = 0; i < aFailedAddressNum; i++) { OutputIp6AddressLine(aFailedAddresses[i]); } } OutputResult(aError); } #endif // (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; otLinkModeConfig linkMode; ClearAllBytes(linkMode); if (aArgs[0].IsEmpty()) { char linkModeString[kLinkModeStringSize]; OutputLine("%s", LinkModeToString(otThreadGetLinkMode(GetInstancePtr()), linkModeString)); ExitNow(); } /** * @cli mode (get,set) * @code * mode rdn * Done * @endcode * @code * mode - * Done * @endcode * @par api_copy * #otThreadSetLinkMode * @cparam mode [@ca{rdn}] * - `-`: no flags set (rx-off-when-idle, minimal Thread device, stable network data) * - `r`: rx-on-when-idle * - `d`: Full Thread Device * - `n`: Full Network Data */ if (aArgs[0] != "-") { for (const char *arg = aArgs[0].GetCString(); *arg != '\0'; arg++) { switch (*arg) { case 'r': linkMode.mRxOnWhenIdle = true; break; case 'd': linkMode.mDeviceType = true; break; case 'n': linkMode.mNetworkData = true; break; default: ExitNow(error = OT_ERROR_INVALID_ARGS); } } } error = otThreadSetLinkMode(GetInstancePtr(), linkMode); exit: return error; } /** * @cli multiradio * @code * multiradio * [15.4, TREL] * Done * @endcode * @par * Get the list of supported radio links by the device. * @par * This command is always available, even when only a single radio is supported by the device. */ template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; OT_UNUSED_VARIABLE(aArgs); if (aArgs[0].IsEmpty()) { bool isFirst = true; OutputFormat("["); #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE OutputFormat("15.4"); isFirst = false; #endif #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE OutputFormat("%sTREL", isFirst ? "" : ", "); #endif OutputLine("]"); OT_UNUSED_VARIABLE(isFirst); } #if OPENTHREAD_CONFIG_MULTI_RADIO else if (aArgs[0] == "neighbor") { otMultiRadioNeighborInfo multiRadioInfo; /** * @cli multiradio neighbor list * @code * multiradio neighbor list * ExtAddr:3a65bc38dbe4a5be, RLOC16:0xcc00, Radios:[15.4(255), TREL(255)] * ExtAddr:17df23452ee4a4be, RLOC16:0x1300, Radios:[15.4(255)] * Done * @endcode * @par api_copy * #otMultiRadioGetNeighborInfo */ if (aArgs[1] == "list") { otNeighborInfoIterator iterator = OT_NEIGHBOR_INFO_ITERATOR_INIT; otNeighborInfo neighInfo; while (otThreadGetNextNeighborInfo(GetInstancePtr(), &iterator, &neighInfo) == OT_ERROR_NONE) { if (otMultiRadioGetNeighborInfo(GetInstancePtr(), &neighInfo.mExtAddress, &multiRadioInfo) != OT_ERROR_NONE) { continue; } OutputFormat("ExtAddr:"); OutputExtAddress(neighInfo.mExtAddress); OutputFormat(", RLOC16:0x%04x, Radios:", neighInfo.mRloc16); OutputMultiRadioInfo(multiRadioInfo); } } else { /** * @cli multiradio neighbor * @code * multiradio neighbor 3a65bc38dbe4a5be * [15.4(255), TREL(255)] * Done * @endcode * @par api_copy * #otMultiRadioGetNeighborInfo * @cparam multiradio neighbor @ca{ext-address} */ otExtAddress extAddress; SuccessOrExit(error = aArgs[1].ParseAsHexString(extAddress.m8)); SuccessOrExit(error = otMultiRadioGetNeighborInfo(GetInstancePtr(), &extAddress, &multiRadioInfo)); OutputMultiRadioInfo(multiRadioInfo); } } #endif // OPENTHREAD_CONFIG_MULTI_RADIO else { ExitNow(error = OT_ERROR_INVALID_COMMAND); } exit: return error; } #if OPENTHREAD_CONFIG_MULTI_RADIO void Interpreter::OutputMultiRadioInfo(const otMultiRadioNeighborInfo &aMultiRadioInfo) { bool isFirst = true; OutputFormat("["); if (aMultiRadioInfo.mSupportsIeee802154) { OutputFormat("15.4(%u)", aMultiRadioInfo.mIeee802154Info.mPreference); isFirst = false; } if (aMultiRadioInfo.mSupportsTrelUdp6) { OutputFormat("%sTREL(%u)", isFirst ? "" : ", ", aMultiRadioInfo.mTrelUdp6Info.mPreference); } OutputLine("]"); } #endif // OPENTHREAD_CONFIG_MULTI_RADIO #if OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; otNeighborInfo neighborInfo; bool isTable; otNeighborInfoIterator iterator = OT_NEIGHBOR_INFO_ITERATOR_INIT; isTable = (aArgs[0] == "table"); if (isTable || (aArgs[0] == "list")) { if (isTable) { static const char *const kNeighborTableTitles[] = { "Role", "RLOC16", "Age", "Avg RSSI", "Last RSSI", "R", "D", "N", "Extended MAC", "Version", }; static const uint8_t kNeighborTableColumnWidths[] = { 6, 8, 5, 10, 11, 1, 1, 1, 18, 9, }; OutputTableHeader(kNeighborTableTitles, kNeighborTableColumnWidths); } while (otThreadGetNextNeighborInfo(GetInstancePtr(), &iterator, &neighborInfo) == OT_ERROR_NONE) { /** * @cli neighbor table * @code * neighbor table * | Role | RLOC16 | Age | Avg RSSI | Last RSSI |R|D|N| Extended MAC | * +------+--------+-----+----------+-----------+-+-+-+------------------+ * | C | 0xcc01 | 96 | -46 | -46 |1|1|1| 1eb9ba8a6522636b | * | R | 0xc800 | 2 | -29 | -29 |1|1|1| 9a91556102c39ddb | * | R | 0xf000 | 3 | -28 | -28 |1|1|1| 0ad7ed6beaa6016d | * Done * @endcode * @par * Prints information in table format about all neighbors. * @par * For `Role`, the only possible values for this table are `C` (Child) or `R` (Router). * @par * The following columns provide information about the device mode of neighbors. * Each column has a value of `0` (off) or `1` (on). * - `R`: RX on when idle * - `D`: Full Thread device * - `N`: Full network data * @sa otThreadGetNextNeighborInfo */ if (isTable) { OutputFormat("| %3c ", neighborInfo.mIsChild ? 'C' : 'R'); OutputFormat("| 0x%04x ", neighborInfo.mRloc16); OutputFormat("| %3lu ", ToUlong(neighborInfo.mAge)); OutputFormat("| %8d ", neighborInfo.mAverageRssi); OutputFormat("| %9d ", neighborInfo.mLastRssi); OutputFormat("|%1d", neighborInfo.mRxOnWhenIdle); OutputFormat("|%1d", neighborInfo.mFullThreadDevice); OutputFormat("|%1d", neighborInfo.mFullNetworkData); OutputFormat("| "); OutputExtAddress(neighborInfo.mExtAddress); OutputLine(" | %7d |", neighborInfo.mVersion); } /** * @cli neighbor list * @code * neighbor list * 0xcc01 0xc800 0xf000 * Done * @endcode * @par * Lists the RLOC16 of each neighbor. */ else { OutputFormat("0x%04x ", neighborInfo.mRloc16); } } OutputNewLine(); } /** * @cli neighbor linkquality * @code * neighbor linkquality * | RLOC16 | Extended MAC | Frame Error | Msg Error | Avg RSS | Last RSS | Age | * +--------+------------------+-------------+-----------+---------+----------+-------+ * | 0xe800 | 9e2fa4e1b84f92db | 0.00 % | 0.00 % | -46 | -48 | 1 | * | 0xc001 | 0ad7ed6beaa6016d | 4.67 % | 0.08 % | -68 | -72 | 10 | * Done * @endcode * @par * Prints link quality information about all neighbors. */ else if (aArgs[0] == "linkquality") { static const char *const kLinkQualityTableTitles[] = { "RLOC16", "Extended MAC", "Frame Error", "Msg Error", "Avg RSS", "Last RSS", "Age", }; static const uint8_t kLinkQualityTableColumnWidths[] = { 8, 18, 13, 11, 9, 10, 7, }; OutputTableHeader(kLinkQualityTableTitles, kLinkQualityTableColumnWidths); while (otThreadGetNextNeighborInfo(GetInstancePtr(), &iterator, &neighborInfo) == OT_ERROR_NONE) { PercentageStringBuffer stringBuffer; OutputFormat("| 0x%04x | ", neighborInfo.mRloc16); OutputExtAddress(neighborInfo.mExtAddress); OutputFormat(" | %9s %% ", PercentageToString(neighborInfo.mFrameErrorRate, stringBuffer)); OutputFormat("| %7s %% ", PercentageToString(neighborInfo.mMessageErrorRate, stringBuffer)); OutputFormat("| %7d ", neighborInfo.mAverageRssi); OutputFormat("| %8d ", neighborInfo.mLastRssi); OutputLine("| %5lu |", ToUlong(neighborInfo.mAge)); } } #if OPENTHREAD_CONFIG_UPTIME_ENABLE /** * @cli neighbor conntime * @code * neighbor conntime * | RLOC16 | Extended MAC | Last Heard (Age) | Connection Time | * +--------+------------------+------------------+------------------+ * | 0x8401 | 1a28be396a14a318 | 00:00:13 | 00:07:59 | * | 0x5c00 | 723ebf0d9eba3264 | 00:00:03 | 00:11:27 | * | 0xe800 | ce53628a1e3f5b3c | 00:00:02 | 00:00:15 | * Done * @endcode * @par * Prints the connection time and age of neighbors. Information per neighbor: * - RLOC16 * - Extended MAC * - Last Heard (Age): Number of seconds since last heard from neighbor. * - Connection Time: Number of seconds since link establishment with neighbor. * Duration intervals are formatted as `{hh}:{mm}:{ss}` for hours, minutes, and seconds if the duration is less * than one day. If the duration is longer than one day, the format is `{dd}d.{hh}:{mm}:{ss}`. * @csa{neighbor conntime list} */ else if (aArgs[0] == "conntime") { /** * @cli neighbor conntime list * @code * neighbor conntime list * 0x8401 1a28be396a14a318 age:63 conn-time:644 * 0x5c00 723ebf0d9eba3264 age:23 conn-time:852 * 0xe800 ce53628a1e3f5b3c age:23 conn-time:180 * Done * @endcode * @par * Prints the connection time and age of neighbors. * This command is similar to `neighbor conntime`, but it displays the information in a list format. The age * and connection time are both displayed in seconds. * @csa{neighbor conntime} */ if (aArgs[1] == "list") { isTable = false; } else { static const char *const kConnTimeTableTitles[] = { "RLOC16", "Extended MAC", "Last Heard (Age)", "Connection Time", }; static const uint8_t kConnTimeTableColumnWidths[] = {8, 18, 18, 18}; isTable = true; OutputTableHeader(kConnTimeTableTitles, kConnTimeTableColumnWidths); } while (otThreadGetNextNeighborInfo(GetInstancePtr(), &iterator, &neighborInfo) == OT_ERROR_NONE) { if (isTable) { char string[OT_DURATION_STRING_SIZE]; OutputFormat("| 0x%04x | ", neighborInfo.mRloc16); OutputExtAddress(neighborInfo.mExtAddress); otConvertDurationInSecondsToString(neighborInfo.mAge, string, sizeof(string)); OutputFormat(" | %16s", string); otConvertDurationInSecondsToString(neighborInfo.mConnectionTime, string, sizeof(string)); OutputLine(" | %16s |", string); } else { OutputFormat("0x%04x ", neighborInfo.mRloc16); OutputExtAddress(neighborInfo.mExtAddress); OutputLine(" age:%lu conn-time:%lu", ToUlong(neighborInfo.mAge), ToUlong(neighborInfo.mConnectionTime)); } } } #endif else { error = OT_ERROR_INVALID_ARGS; } return error; } #endif // OPENTHREAD_FTD /** * @cli netstat * @code * netstat * | Local Address | Peer Address | * +-------------------------------------------------+-------------------------------------------------+ * | [0:0:0:0:0:0:0:0]:49153 | [0:0:0:0:0:0:0:0]:0 | * | [0:0:0:0:0:0:0:0]:49152 | [0:0:0:0:0:0:0:0]:0 | * | [0:0:0:0:0:0:0:0]:61631 | [0:0:0:0:0:0:0:0]:0 | * | [0:0:0:0:0:0:0:0]:19788 | [0:0:0:0:0:0:0:0]:0 | * Done * @endcode * @par api_copy * #otUdpGetSockets */ template <> otError Interpreter::Process(Arg aArgs[]) { OT_UNUSED_VARIABLE(aArgs); static const char *const kNetstatTableTitles[] = {"Local Address", "Peer Address"}; static const uint8_t kNetstatTableColumnWidths[] = {49, 49}; char string[OT_IP6_SOCK_ADDR_STRING_SIZE]; OutputTableHeader(kNetstatTableTitles, kNetstatTableColumnWidths); for (const otUdpSocket *socket = otUdpGetSockets(GetInstancePtr()); socket != nullptr; socket = socket->mNext) { otIp6SockAddrToString(&socket->mSockName, string, sizeof(string)); OutputFormat("| %-47s ", string); otIp6SockAddrToString(&socket->mPeerName, string, sizeof(string)); OutputLine("| %-47s |", string); } return OT_ERROR_NONE; } #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_INVALID_COMMAND; otServiceConfig cfg; if (aArgs[0].IsEmpty()) { otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT; otServiceConfig config; while (otServerGetNextService(GetInstancePtr(), &iterator, &config) == OT_ERROR_NONE) { mNetworkData.OutputService(config); } error = OT_ERROR_NONE; } else { uint16_t length; SuccessOrExit(error = aArgs[1].ParseAsUint32(cfg.mEnterpriseNumber)); length = sizeof(cfg.mServiceData); SuccessOrExit(error = aArgs[2].ParseAsHexString(length, cfg.mServiceData)); VerifyOrExit(length > 0, error = OT_ERROR_INVALID_ARGS); cfg.mServiceDataLength = static_cast(length); /** * @cli service add * @code * service add 44970 112233 aabbcc * Done * @endcode * @code * netdata register * Done * @endcode * @cparam service add @ca{enterpriseNumber} @ca{serviceData} @ca{serverData} * @par * Adds service to the network data. * @par * - enterpriseNumber: IANA enterprise number * - serviceData: Hex-encoded binary service data * - serverData: Hex-encoded binary server data * @par * Note: For each change in service registration to take effect, run * the `netdata register` command after running the `service add` command to notify the leader. * @sa otServerAddService * @csa{netdata register} */ if (aArgs[0] == "add") { length = sizeof(cfg.mServerConfig.mServerData); SuccessOrExit(error = aArgs[3].ParseAsHexString(length, cfg.mServerConfig.mServerData)); VerifyOrExit(length > 0, error = OT_ERROR_INVALID_ARGS); cfg.mServerConfig.mServerDataLength = static_cast(length); cfg.mServerConfig.mStable = true; error = otServerAddService(GetInstancePtr(), &cfg); } /** * @cli service remove * @code * service remove 44970 112233 * Done * @endcode * @code * netdata register * Done * @endcode * @cparam service remove @ca{enterpriseNumber} @ca{serviceData} * @par * Removes service from the network data. * @par * - enterpriseNumber: IANA enterprise number * - serviceData: Hex-encoded binary service data * @par * Note: For each change in service registration to take effect, run * the `netdata register` command after running the `service remove` command to notify the leader. * @sa otServerRemoveService * @csa{netdata register} */ else if (aArgs[0] == "remove") { error = otServerRemoveService(GetInstancePtr(), cfg.mEnterpriseNumber, cfg.mServiceData, cfg.mServiceDataLength); } } exit: return error; } #endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mNetworkData.Process(aArgs); } #if OPENTHREAD_FTD /** * @cli networkidtimeout (get,set) * @code * networkidtimeout 120 * Done * @endcode * @code * networkidtimeout * 120 * Done * @endcode * @cparam networkidtimeout [@ca{timeout}] * Use the optional `timeout` argument to set the value of the `NETWORK_ID_TIMEOUT` parameter. * @par * Gets or sets the `NETWORK_ID_TIMEOUT` parameter. * @note This command is reserved for testing and demo purposes only. * Changing settings with this API will render a production application * non-compliant with the Thread Specification. * @sa otThreadGetNetworkIdTimeout * @sa otThreadSetNetworkIdTimeout */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessGetSet(aArgs, otThreadGetNetworkIdTimeout, otThreadSetNetworkIdTimeout); } #endif template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli networkkey * @code * networkkey * 00112233445566778899aabbccddeeff * Done * @endcode * @par api_copy * #otThreadGetNetworkKey */ if (aArgs[0].IsEmpty()) { otNetworkKey networkKey; otThreadGetNetworkKey(GetInstancePtr(), &networkKey); OutputBytesLine(networkKey.m8); } /** * @cli networkkey (key) * @code * networkkey 00112233445566778899aabbccddeeff * Done * @endcode * @par api_copy * #otThreadSetNetworkKey * @cparam networkkey @ca{key} */ else { otNetworkKey key; SuccessOrExit(error = aArgs[0].ParseAsHexString(key.m8)); SuccessOrExit(error = otThreadSetNetworkKey(GetInstancePtr(), &key)); } exit: return error; } #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; if (aArgs[0].IsEmpty()) { OutputLine("0x%08lx", ToUlong(otThreadGetNetworkKeyRef(GetInstancePtr()))); } else { otNetworkKeyRef keyRef; SuccessOrExit(error = aArgs[0].ParseAsUint32(keyRef)); SuccessOrExit(error = otThreadSetNetworkKeyRef(GetInstancePtr(), keyRef)); } exit: return error; } #endif /** * @cli networkname * @code * networkname * OpenThread * Done * @endcode * @par api_copy * #otThreadGetNetworkName */ template <> otError Interpreter::Process(Arg aArgs[]) { /** * @cli networkname (name) * @code * networkname OpenThread * Done * @endcode * @par api_copy * #otThreadSetNetworkName * @cparam networkname @ca{name} * @par * Note: The current commissioning credential becomes stale after changing this value. Use `pskc` to reset. */ return ProcessGetSet(aArgs, otThreadGetNetworkName, otThreadSetNetworkName); } #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli networktime * @code * networktime * Network Time: 21084154us (synchronized) * Time Sync Period: 100s * XTAL Threshold: 300ppm * Done * @endcode * @par * Gets the Thread network time and the time sync parameters. * @sa otNetworkTimeGet * @sa otNetworkTimeGetSyncPeriod * @sa otNetworkTimeGetXtalThreshold */ if (aArgs[0].IsEmpty()) { uint64_t time; otNetworkTimeStatus networkTimeStatus; networkTimeStatus = otNetworkTimeGet(GetInstancePtr(), &time); OutputFormat("Network Time: "); OutputUint64(time); OutputFormat("us"); switch (networkTimeStatus) { case OT_NETWORK_TIME_UNSYNCHRONIZED: OutputLine(" (unsynchronized)"); break; case OT_NETWORK_TIME_RESYNC_NEEDED: OutputLine(" (resync needed)"); break; case OT_NETWORK_TIME_SYNCHRONIZED: OutputLine(" (synchronized)"); break; default: break; } OutputLine("Time Sync Period: %us", otNetworkTimeGetSyncPeriod(GetInstancePtr())); OutputLine("XTAL Threshold: %uppm", otNetworkTimeGetXtalThreshold(GetInstancePtr())); } /** * @cli networktime (set) * @code * networktime 100 300 * Done * @endcode * @cparam networktime @ca{timesyncperiod} @ca{xtalthreshold} * @par * Sets the time sync parameters. * * `timesyncperiod`: The time synchronization period, in seconds. * * `xtalthreshold`: The XTAL accuracy threshold for a device to become Router-Capable device, in PPM. * @sa otNetworkTimeSetSyncPeriod * @sa otNetworkTimeSetXtalThreshold */ else { uint16_t period; uint16_t xtalThreshold; SuccessOrExit(error = aArgs[0].ParseAsUint16(period)); SuccessOrExit(error = aArgs[1].ParseAsUint16(xtalThreshold)); SuccessOrExit(error = otNetworkTimeSetSyncPeriod(GetInstancePtr(), period)); SuccessOrExit(error = otNetworkTimeSetXtalThreshold(GetInstancePtr(), xtalThreshold)); } exit: return error; } #endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE #if OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { constexpr uint8_t kRouterIdOffset = 10; // Bit offset of Router ID in RLOC16 constexpr uint16_t kInvalidRloc16 = 0xfffe; otError error = OT_ERROR_NONE; uint16_t destRloc16; uint16_t nextHopRloc16; uint8_t pathCost; /** * @cli nexthop * @code * nexthop * | ID |NxtHop| Cost | * +------+------+------+ * | 9 | 9 | 1 | * | 25 | 25 | 0 | * | 30 | 30 | 1 | * | 46 | - | - | * | 50 | 30 | 3 | * | 60 | 30 | 2 | * Done * @endcode * @par * Output table of allocated Router IDs and current next hop and path * cost for each router. * @sa otThreadGetNextHopAndPathCost * @sa otThreadIsRouterIdAllocated */ if (aArgs[0].IsEmpty()) { static const char *const kNextHopTableTitles[] = { "ID", "NxtHop", "Cost", }; static const uint8_t kNextHopTableColumnWidths[] = { 6, 6, 6, }; OutputTableHeader(kNextHopTableTitles, kNextHopTableColumnWidths); for (uint8_t routerId = 0; routerId <= OT_NETWORK_MAX_ROUTER_ID; routerId++) { if (!otThreadIsRouterIdAllocated(GetInstancePtr(), routerId)) { continue; } destRloc16 = routerId; destRloc16 <<= kRouterIdOffset; otThreadGetNextHopAndPathCost(GetInstancePtr(), destRloc16, &nextHopRloc16, &pathCost); OutputFormat("| %4u | ", routerId); if (nextHopRloc16 != kInvalidRloc16) { OutputLine("%4u | %4u |", nextHopRloc16 >> kRouterIdOffset, pathCost); } else { OutputLine("%4s | %4s |", "-", "-"); } } } /** * @cli nexthop (get) * @code * nexthop 0xc000 * 0xc000 cost:0 * Done * @endcode * @code * nexthop 0x8001 * 0x2000 cost:3 * Done * @endcode * @cparam nexthop @ca{rloc16} * @par api_copy * #otThreadGetNextHopAndPathCost */ else { SuccessOrExit(error = aArgs[0].ParseAsUint16(destRloc16)); otThreadGetNextHopAndPathCost(GetInstancePtr(), destRloc16, &nextHopRloc16, &pathCost); OutputLine("0x%04x cost:%u", nextHopRloc16, pathCost); } exit: return error; } #if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli meshdiag topology * @code * meshdiag topology * id:02 rloc16:0x0800 ext-addr:8aa57d2c603fe16c ver:4 - me - leader * 3-links:{ 46 } * id:46 rloc16:0xb800 ext-addr:fe109d277e0175cc ver:4 * 3-links:{ 02 51 57 } * id:33 rloc16:0x8400 ext-addr:d2e511a146b9e54d ver:4 * 3-links:{ 51 57 } * id:51 rloc16:0xcc00 ext-addr:9aab43ababf05352 ver:4 * 3-links:{ 33 57 } * 2-links:{ 46 } * id:57 rloc16:0xe400 ext-addr:dae9c4c0e9da55ff ver:4 * 3-links:{ 46 51 } * 1-links:{ 33 } * Done * @endcode * @par * Discover network topology (list of routers and their connections). * Parameters are optional and indicate additional items to discover. Can be added in any order. * * `ip6-addrs` to discover the list of IPv6 addresses of every router. * * `children` to discover the child table of every router. * @par * Information per router: * * Router ID * * RLOC16 * * Extended MAC address * * Thread Version (if known) * * Whether the router is this device is itself (`me`) * * Whether the router is the parent of this device when device is a child (`parent`) * * Whether the router is `leader` * * Whether the router acts as a border router providing external connectivity (`br`) * * List of routers to which this router has a link: * * `3-links`: Router IDs to which this router has a incoming link with link quality 3 * * `2-links`: Router IDs to which this router has a incoming link with link quality 2 * * `1-links`: Router IDs to which this router has a incoming link with link quality 1 * * If a list if empty, it is omitted in the out. * * If `ip6-addrs`, list of IPv6 addresses of the router * * If `children`, list of all children of the router. Information per child: * * RLOC16 * * Incoming Link Quality from perspective of parent to child (zero indicates unknown) * * Child Device mode (`r` rx-on-when-idle, `d` Full Thread Device, `n` Full Network Data, `-` no flags set) * * Whether the child is this device itself (`me`) * * Whether the child acts as a border router providing external connectivity (`br`) * @cparam meshdiag topology [@ca{ip6-addrs}] [@ca{children}] * @sa otMeshDiagDiscoverTopology */ if (aArgs[0] == "topology") { otMeshDiagDiscoverConfig config; config.mDiscoverIp6Addresses = false; config.mDiscoverChildTable = false; aArgs++; for (; !aArgs->IsEmpty(); aArgs++) { if (*aArgs == "ip6-addrs") { config.mDiscoverIp6Addresses = true; } else if (*aArgs == "children") { config.mDiscoverChildTable = true; } else { ExitNow(error = OT_ERROR_INVALID_ARGS); } } SuccessOrExit(error = otMeshDiagDiscoverTopology(GetInstancePtr(), &config, HandleMeshDiagDiscoverDone, this)); error = OT_ERROR_PENDING; } /** * @cli meshdiag childtable * @code * meshdiag childtable 0x6400 * rloc16:0x6402 ext-addr:8e6f4d323bbed1fe ver:4 * timeout:120 age:36 supvn:129 q-msg:0 * rx-on:yes type:ftd full-net:yes * rss - ave:-20 last:-20 margin:80 * err-rate - frame:11.51% msg:0.76% * conn-time:00:11:07 * csl - sync:no period:0 timeout:0 channel:0 * rloc16:0x6403 ext-addr:ee24e64ecf8c079a ver:4 * timeout:120 age:19 supvn:129 q-msg:0 * rx-on:no type:mtd full-net:no * rss - ave:-20 last:-20 margin:80 * err-rate - frame:0.73% msg:0.00% * conn-time:01:08:53 * csl - sync:no period:0 timeout:0 channel:0 * Done * @endcode * @par * Start a query for child table of a router with a given RLOC16. * Output lists all child entries. Information per child: * - RLOC16 * - Extended MAC address * - Thread Version * - Timeout (in seconds) * - Age (seconds since last heard) * - Supervision interval (in seconds) * - Number of queued messages (in case child is sleepy) * - Device Mode * - RSS (average and last) * - Error rates: frame tx (at MAC layer), IPv6 message tx (above MAC) * - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format) * - CSL info: * - If synchronized * - Period (in unit of 10-symbols-time) * - Timeout (in seconds) * * @cparam meshdiag childtable @ca{router-rloc16} * @sa otMeshDiagQueryChildTable */ else if (aArgs[0] == "childtable") { uint16_t routerRloc16; SuccessOrExit(error = aArgs[1].ParseAsUint16(routerRloc16)); VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = otMeshDiagQueryChildTable(GetInstancePtr(), routerRloc16, HandleMeshDiagQueryChildTableResult, this)); error = OT_ERROR_PENDING; } /** * @cli meshdiag childip6 * @code * meshdiag childip6 0xdc00 * child-rloc16: 0xdc02 * fdde:ad00:beef:0:ded8:cd58:b73:2c21 * fd00:2:0:0:c24a:456:3b6b:c597 * fd00:1:0:0:120b:95fe:3ecc:d238 * child-rloc16: 0xdc03 * fdde:ad00:beef:0:3aa6:b8bf:e7d6:eefe * fd00:2:0:0:8ff8:a188:7436:6720 * fd00:1:0:0:1fcf:5495:790a:370f * Done * @endcode * @par * Send a query to a parent to retrieve the IPv6 addresses of all its MTD children. * @cparam meshdiag childip6 @ca{parent-rloc16} * @sa otMeshDiagQueryChildrenIp6Addrs */ else if (aArgs[0] == "childip6") { uint16_t parentRloc16; SuccessOrExit(error = aArgs[1].ParseAsUint16(parentRloc16)); VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = otMeshDiagQueryChildrenIp6Addrs(GetInstancePtr(), parentRloc16, HandleMeshDiagQueryChildIp6Addrs, this)); error = OT_ERROR_PENDING; } /** * @cli meshdiag routerneighbortable * @code * meshdiag routerneighbortable 0x7400 * rloc16:0x9c00 ext-addr:764788cf6e57a4d2 ver:4 * rss - ave:-20 last:-20 margin:80 * err-rate - frame:1.38% msg:0.00% * conn-time:01:54:02 * rloc16:0x7c00 ext-addr:4ed24fceec9bf6d3 ver:4 * rss - ave:-20 last:-20 margin:80 * err-rate - frame:0.72% msg:0.00% * conn-time:00:11:27 * Done * @endcode * @par * Start a query for router neighbor table of a router with a given RLOC16. * Output lists all router neighbor entries. Information per entry: * - RLOC16 * - Extended MAC address * - Thread Version * - RSS (average and last) and link margin * - Error rates, frame tx (at MAC layer), IPv6 message tx (above MAC) * - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format) * @cparam meshdiag routerneighbortable @ca{router-rloc16} * @sa otMeshDiagQueryRouterNeighborTable */ else if (aArgs[0] == "routerneighbortable") { uint16_t routerRloc16; SuccessOrExit(error = aArgs[1].ParseAsUint16(routerRloc16)); VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = otMeshDiagQueryRouterNeighborTable(GetInstancePtr(), routerRloc16, HandleMeshDiagQueryRouterNeighborTableResult, this)); error = OT_ERROR_PENDING; } else { error = OT_ERROR_INVALID_COMMAND; } exit: return error; } void Interpreter::HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo, void *aContext) { reinterpret_cast(aContext)->HandleMeshDiagDiscoverDone(aError, aRouterInfo); } void Interpreter::HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo) { VerifyOrExit(aRouterInfo != nullptr); OutputFormat("id:%02u rloc16:0x%04x ext-addr:", aRouterInfo->mRouterId, aRouterInfo->mRloc16); OutputExtAddress(aRouterInfo->mExtAddress); if (aRouterInfo->mVersion != OT_MESH_DIAG_VERSION_UNKNOWN) { OutputFormat(" ver:%u", aRouterInfo->mVersion); } if (aRouterInfo->mIsThisDevice) { OutputFormat(" - me"); } if (aRouterInfo->mIsThisDeviceParent) { OutputFormat(" - parent"); } if (aRouterInfo->mIsLeader) { OutputFormat(" - leader"); } if (aRouterInfo->mIsBorderRouter) { OutputFormat(" - br"); } OutputNewLine(); for (uint8_t linkQuality = 3; linkQuality > 0; linkQuality--) { bool hasLinkQuality = false; for (uint8_t entryQuality : aRouterInfo->mLinkQualities) { if (entryQuality == linkQuality) { hasLinkQuality = true; break; } } if (hasLinkQuality) { OutputFormat(kIndentSize, "%u-links:{ ", linkQuality); for (uint8_t id = 0; id < static_cast(OT_ARRAY_LENGTH(aRouterInfo->mLinkQualities)); id++) { if (aRouterInfo->mLinkQualities[id] == linkQuality) { OutputFormat("%02u ", id); } } OutputLine("}"); } } if (aRouterInfo->mIp6AddrIterator != nullptr) { otIp6Address ip6Address; OutputLine(kIndentSize, "ip6-addrs:"); while (otMeshDiagGetNextIp6Address(aRouterInfo->mIp6AddrIterator, &ip6Address) == OT_ERROR_NONE) { OutputSpaces(kIndentSize * 2); OutputIp6AddressLine(ip6Address); } } if (aRouterInfo->mChildIterator != nullptr) { otMeshDiagChildInfo childInfo; char linkModeString[kLinkModeStringSize]; bool isFirst = true; while (otMeshDiagGetNextChildInfo(aRouterInfo->mChildIterator, &childInfo) == OT_ERROR_NONE) { if (isFirst) { OutputLine(kIndentSize, "children:"); isFirst = false; } OutputFormat(kIndentSize * 2, "rloc16:0x%04x lq:%u, mode:%s", childInfo.mRloc16, childInfo.mLinkQuality, LinkModeToString(childInfo.mMode, linkModeString)); if (childInfo.mIsThisDevice) { OutputFormat(" - me"); } if (childInfo.mIsBorderRouter) { OutputFormat(" - br"); } OutputNewLine(); } if (isFirst) { OutputLine(kIndentSize, "children: none"); } } exit: OutputResult(aError); } void Interpreter::HandleMeshDiagQueryChildTableResult(otError aError, const otMeshDiagChildEntry *aChildEntry, void *aContext) { reinterpret_cast(aContext)->HandleMeshDiagQueryChildTableResult(aError, aChildEntry); } void Interpreter::HandleMeshDiagQueryChildTableResult(otError aError, const otMeshDiagChildEntry *aChildEntry) { PercentageStringBuffer stringBuffer; char string[OT_DURATION_STRING_SIZE]; VerifyOrExit(aChildEntry != nullptr); OutputFormat("rloc16:0x%04x ext-addr:", aChildEntry->mRloc16); OutputExtAddress(aChildEntry->mExtAddress); OutputLine(" ver:%u", aChildEntry->mVersion); OutputLine(kIndentSize, "timeout:%lu age:%lu supvn:%u q-msg:%u", ToUlong(aChildEntry->mTimeout), ToUlong(aChildEntry->mAge), aChildEntry->mSupervisionInterval, aChildEntry->mQueuedMessageCount); OutputLine(kIndentSize, "rx-on:%s type:%s full-net:%s", aChildEntry->mRxOnWhenIdle ? "yes" : "no", aChildEntry->mDeviceTypeFtd ? "ftd" : "mtd", aChildEntry->mFullNetData ? "yes" : "no"); OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aChildEntry->mAverageRssi, aChildEntry->mLastRssi, aChildEntry->mLinkMargin); if (aChildEntry->mSupportsErrRate) { OutputFormat(kIndentSize, "err-rate - frame:%s%% ", PercentageToString(aChildEntry->mFrameErrorRate, stringBuffer)); OutputLine("msg:%s%% ", PercentageToString(aChildEntry->mMessageErrorRate, stringBuffer)); } otConvertDurationInSecondsToString(aChildEntry->mConnectionTime, string, sizeof(string)); OutputLine(kIndentSize, "conn-time:%s", string); OutputLine(kIndentSize, "csl - sync:%s period:%u timeout:%lu channel:%u", aChildEntry->mCslSynchronized ? "yes" : "no", aChildEntry->mCslPeriod, ToUlong(aChildEntry->mCslTimeout), aChildEntry->mCslChannel); exit: OutputResult(aError); } void Interpreter::HandleMeshDiagQueryRouterNeighborTableResult(otError aError, const otMeshDiagRouterNeighborEntry *aNeighborEntry, void *aContext) { reinterpret_cast(aContext)->HandleMeshDiagQueryRouterNeighborTableResult(aError, aNeighborEntry); } void Interpreter::HandleMeshDiagQueryRouterNeighborTableResult(otError aError, const otMeshDiagRouterNeighborEntry *aNeighborEntry) { PercentageStringBuffer stringBuffer; char string[OT_DURATION_STRING_SIZE]; VerifyOrExit(aNeighborEntry != nullptr); OutputFormat("rloc16:0x%04x ext-addr:", aNeighborEntry->mRloc16); OutputExtAddress(aNeighborEntry->mExtAddress); OutputLine(" ver:%u", aNeighborEntry->mVersion); OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aNeighborEntry->mAverageRssi, aNeighborEntry->mLastRssi, aNeighborEntry->mLinkMargin); if (aNeighborEntry->mSupportsErrRate) { OutputFormat(kIndentSize, "err-rate - frame:%s%% ", PercentageToString(aNeighborEntry->mFrameErrorRate, stringBuffer)); OutputLine("msg:%s%% ", PercentageToString(aNeighborEntry->mMessageErrorRate, stringBuffer)); } otConvertDurationInSecondsToString(aNeighborEntry->mConnectionTime, string, sizeof(string)); OutputLine(kIndentSize, "conn-time:%s", string); exit: OutputResult(aError); } void Interpreter::HandleMeshDiagQueryChildIp6Addrs(otError aError, uint16_t aChildRloc16, otMeshDiagIp6AddrIterator *aIp6AddrIterator, void *aContext) { reinterpret_cast(aContext)->HandleMeshDiagQueryChildIp6Addrs(aError, aChildRloc16, aIp6AddrIterator); } void Interpreter::HandleMeshDiagQueryChildIp6Addrs(otError aError, uint16_t aChildRloc16, otMeshDiagIp6AddrIterator *aIp6AddrIterator) { otIp6Address ip6Address; VerifyOrExit(aError == OT_ERROR_NONE || aError == OT_ERROR_PENDING); VerifyOrExit(aIp6AddrIterator != nullptr); OutputLine("child-rloc16: 0x%04x", aChildRloc16); while (otMeshDiagGetNextIp6Address(aIp6AddrIterator, &ip6Address) == OT_ERROR_NONE) { OutputSpaces(kIndentSize); OutputIp6AddressLine(ip6Address); } exit: OutputResult(aError); } #endif // OPENTHREAD_CONFIG_MESH_DIAG_ENABLE #endif // OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli panid * @code * panid * 0xdead * Done * @endcode * @par api_copy * #otLinkGetPanId */ if (aArgs[0].IsEmpty()) { OutputLine("0x%04x", otLinkGetPanId(GetInstancePtr())); } /** * @cli panid (panid) * @code * panid 0xdead * Done * @endcode * @par api_copy * #otLinkSetPanId * @cparam panid @ca{panid} */ else { error = ProcessSet(aArgs, otLinkSetPanId); } return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli parent * @code * parent * Ext Addr: be1857c6c21dce55 * Rloc: 5c00 * Link Quality In: 3 * Link Quality Out: 3 * Age: 20 * Version: 4 * Done * @endcode * @sa otThreadGetParentInfo * @par * Get the diagnostic information for a Thread Router as parent. * @par * When operating as a Thread Router when OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE is enabled, this command * will return the cached information from when the device was previously attached as a Thread Child. Returning * cached information is necessary to support the Thread Test Harness - Test Scenario 8.2.x requests the former * parent (i.e. %Joiner Router's) MAC address even if the device has already promoted to a router. * @par * Note: When OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE is enabled, this command will return two extra lines with * information relevant for CSL Receiver operation. */ if (aArgs[0].IsEmpty()) { otRouterInfo parentInfo; SuccessOrExit(error = otThreadGetParentInfo(GetInstancePtr(), &parentInfo)); OutputFormat("Ext Addr: "); OutputExtAddressLine(parentInfo.mExtAddress); OutputLine("Rloc: %x", parentInfo.mRloc16); OutputLine("Link Quality In: %u", parentInfo.mLinkQualityIn); OutputLine("Link Quality Out: %u", parentInfo.mLinkQualityOut); OutputLine("Age: %lu", ToUlong(parentInfo.mAge)); OutputLine("Version: %u", parentInfo.mVersion); #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE OutputLine("CSL clock accuracy: %u", parentInfo.mCslClockAccuracy); OutputLine("CSL uncertainty: %u", parentInfo.mCslUncertainty); #endif } /** * @cli parent search * @code * parent search * Done * @endcode * @par api_copy * #otThreadSearchForBetterParent */ else if (aArgs[0] == "search") { error = otThreadSearchForBetterParent(GetInstancePtr()); } else { error = OT_ERROR_INVALID_ARGS; } exit: return error; } #if OPENTHREAD_FTD /** * @cli parentpriority (get,set) * @code * parentpriority * 1 * Done * @endcode * @code * parentpriority 1 * Done * @endcode * @cparam parentpriority [@ca{parentpriority}] * @par * Gets or sets the assigned parent priority value: 1, 0, -1 or -2. -2 means not assigned. * @sa otThreadGetParentPriority * @sa otThreadSetParentPriority */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessGetSet(aArgs, otThreadGetParentPriority, otThreadSetParentPriority); } #endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE template <> otError Interpreter::Process(Arg *aArgs) { uint8_t minRouterId; uint8_t maxRouterId; otError error = OT_ERROR_NONE; if (aArgs[0].IsEmpty()) { otThreadGetRouterIdRange(GetInstancePtr(), &minRouterId, &maxRouterId); OutputLine("%u %u", minRouterId, maxRouterId); } else { SuccessOrExit(error = aArgs[0].ParseAsUint8(minRouterId)); SuccessOrExit(error = aArgs[1].ParseAsUint8(maxRouterId)); VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); error = otThreadSetRouterIdRange(GetInstancePtr(), minRouterId, maxRouterId); } exit: return error; } #endif #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE /** * @cli ping * @code * ping fd00:db8:0:0:76b:6a05:3ae9:a61a * 16 bytes from fd00:db8:0:0:76b:6a05:3ae9:a61a: icmp_seq=5 hlim=64 time=0ms * 1 packets transmitted, 1 packets received. Packet loss = 0.0%. Round-trip min/avg/max = 0/0.0/0 ms. * Done * @endcode * @code * ping -I fd00:db8:0:0:76b:6a05:3ae9:a61a ff02::1 100 1 1 1 * 108 bytes from fd00:db8:0:0:f605:fb4b:d429:d59a: icmp_seq=4 hlim=64 time=7ms * 1 packets transmitted, 1 packets received. Round-trip min/avg/max = 7/7.0/7 ms. * Done * @endcode * @code * ping 172.17.0.1 * Pinging synthesized IPv6 address: fdde:ad00:beef:2:0:0:ac11:1 * 16 bytes from fdde:ad00:beef:2:0:0:ac11:1: icmp_seq=5 hlim=64 time=0ms * 1 packets transmitted, 1 packets received. Packet loss = 0.0%. Round-trip min/avg/max = 0/0.0/0 ms. * Done * @endcode * @cparam ping [@ca{async}] [@ca{-I source}] [@ca{-m}] @ca{ipaddrc} [@ca{size}] [@ca{count}] [@ca{interval}] [@ca{hoplimit}] [@ca{timeout}] * @par * Send an ICMPv6 Echo Request. * @par * The address can be an IPv4 address, which will be synthesized to an IPv6 address using the preferred NAT64 prefix * from the network data. * @par * The optional `-m` flag sets the multicast loop flag, which allows looping back pings to multicast addresses that the * device itself is subscribed to. * @par * Note: The command will return InvalidState when the preferred NAT64 prefix is unavailable. * @sa otPingSenderPing */ template <> otError Interpreter::Process(Arg aArgs[]) { return mPing.Process(aArgs); } #endif // OPENTHREAD_CONFIG_PING_SENDER_ENABLE /** * @cli platform * @code * platform * NRF52840 * Done * @endcode * @par * Print the current platform */ template <> otError Interpreter::Process(Arg aArgs[]) { OT_UNUSED_VARIABLE(aArgs); OutputLine("%s", OPENTHREAD_CONFIG_PLATFORM_INFO); return OT_ERROR_NONE; } /** * @cli pollperiod (get,set) * @code * pollperiod * 0 * Done * @endcode * @code * pollperiod 10 * Done * @endcode * @sa otLinkGetPollPeriod * @sa otLinkSetPollPeriod * @par * Get or set the customized data poll period of sleepy end device (milliseconds). Only for certification test. */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessGetSet(aArgs, otLinkGetPollPeriod, otLinkSetPollPeriod); } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli promiscuous * @code * promiscuous * Disabled * Done * @endcode * @par api_copy * #otLinkIsPromiscuous * @sa otPlatRadioGetPromiscuous */ if (aArgs[0].IsEmpty()) { OutputEnabledDisabledStatus(otLinkIsPromiscuous(GetInstancePtr()) && otPlatRadioGetPromiscuous(GetInstancePtr())); } else { bool enable; SuccessOrExit(error = ParseEnableOrDisable(aArgs[0], enable)); /** * @cli promiscuous (enable,disable) * @code * promiscuous enable * Done * @endcode * @code * promiscuous disable * Done * @endcode * @cparam promiscuous @ca{enable|disable} * @par api_copy * #otLinkSetPromiscuous */ if (!enable) { otLinkSetPcapCallback(GetInstancePtr(), nullptr, nullptr); } SuccessOrExit(error = otLinkSetPromiscuous(GetInstancePtr(), enable)); if (enable) { otLinkSetPcapCallback(GetInstancePtr(), &HandleLinkPcapReceive, this); } } exit: return error; } void Interpreter::HandleLinkPcapReceive(const otRadioFrame *aFrame, bool aIsTx, void *aContext) { static_cast(aContext)->HandleLinkPcapReceive(aFrame, aIsTx); } void Interpreter::HandleLinkPcapReceive(const otRadioFrame *aFrame, bool aIsTx) { otLogHexDumpInfo info; info.mDataBytes = aFrame->mPsdu; info.mDataLength = aFrame->mLength; info.mTitle = (aIsTx) ? "TX" : "RX"; info.mIterator = 0; OutputNewLine(); while (otLogGenerateNextHexDumpLine(&info) == OT_ERROR_NONE) { OutputLine("%s", info.mLine); } } #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli prefix * @code * prefix * 2001:dead:beef:cafe::/64 paros med * - fd00:7d03:7d03:7d03::/64 prosD med * Done * @endcode * @par * Get the prefix list in the local Network Data. * @note For the Thread 1.2 border router with backbone capability, the local Domain Prefix * is listed as well and includes the `D` flag. If backbone functionality is disabled, a dash * `-` is printed before the local Domain Prefix. * @par * For more information about #otBorderRouterConfig flags, refer to @overview. * @sa otBorderRouterGetNextOnMeshPrefix */ if (aArgs[0].IsEmpty()) { otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT; otBorderRouterConfig config; while (otBorderRouterGetNextOnMeshPrefix(GetInstancePtr(), &iterator, &config) == OT_ERROR_NONE) { mNetworkData.OutputPrefix(config); } #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE if (otBackboneRouterGetState(GetInstancePtr()) == OT_BACKBONE_ROUTER_STATE_DISABLED) { SuccessOrExit(otBackboneRouterGetDomainPrefix(GetInstancePtr(), &config)); OutputFormat("- "); mNetworkData.OutputPrefix(config); } #endif } /** * @cli prefix add * @code * prefix add 2001:dead:beef:cafe::/64 paros med * Done * @endcode * @code * prefix add fd00:7d03:7d03:7d03::/64 prosD low * Done * @endcode * @cparam prefix add @ca{prefix} [@ca{padcrosnD}] [@ca{high}|@ca{med}|@ca{low}] * OT CLI uses mapped arguments to configure #otBorderRouterConfig values. @moreinfo{the @overview}. * @par * Adds a valid prefix to the Network Data. * @sa otBorderRouterAddOnMeshPrefix */ else if (aArgs[0] == "add") { otBorderRouterConfig config; SuccessOrExit(error = ParsePrefix(aArgs + 1, config)); error = otBorderRouterAddOnMeshPrefix(GetInstancePtr(), &config); } /** * @cli prefix remove * @code * prefix remove 2001:dead:beef:cafe::/64 * Done * @endcode * @par api_copy * #otBorderRouterRemoveOnMeshPrefix */ else if (aArgs[0] == "remove") { otIp6Prefix prefix; SuccessOrExit(error = aArgs[1].ParseAsIp6Prefix(prefix)); error = otBorderRouterRemoveOnMeshPrefix(GetInstancePtr(), &prefix); } /** * @cli prefix meshlocal * @code * prefix meshlocal * fdde:ad00:beef:0::/64 * Done * @endcode * @par * Get the mesh local prefix. */ else if (aArgs[0] == "meshlocal") { if (aArgs[1].IsEmpty()) { OutputIp6PrefixLine(*otThreadGetMeshLocalPrefix(GetInstancePtr())); } else { otIp6Prefix prefix; SuccessOrExit(error = aArgs[1].ParseAsIp6Prefix(prefix)); VerifyOrExit(prefix.mLength == OT_IP6_PREFIX_BITSIZE, error = OT_ERROR_INVALID_ARGS); error = otThreadSetMeshLocalPrefix(GetInstancePtr(), reinterpret_cast(&prefix.mPrefix)); } } else { error = OT_ERROR_INVALID_COMMAND; } exit: return error; } #endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE /** * @cli preferrouterid * @code * preferrouterid 16 * Done * @endcode * @cparam preferrouterid @ca{routerid} * @par * Specifies the preferred router ID that the leader should provide when solicited. * @sa otThreadSetPreferredRouterId */ #if OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessSet(aArgs, otThreadSetPreferredRouterId); } #endif #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE && OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessEnableDisable(aArgs, otLinkIsRadioFilterEnabled, otLinkSetRadioFilterEnabled); } #endif #if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE inline unsigned long UsToSInt(uint64_t aUs) { return ToUlong(static_cast(aUs / 1000000)); } inline unsigned long UsToSDec(uint64_t aUs) { return ToUlong(static_cast(aUs % 1000000)); } void Interpreter::OutputRadioStatsTime(const char *aTimeName, uint64_t aTimeUs, uint64_t aTotalTimeUs) { uint32_t timePercentInt = static_cast(aTimeUs * 100 / aTotalTimeUs); uint32_t timePercentDec = static_cast((aTimeUs * 100 % aTotalTimeUs) * 100 / aTotalTimeUs); OutputLine("%s Time: %lu.%06lus (%lu.%02lu%%)", aTimeName, UsToSInt(aTimeUs), UsToSDec(aTimeUs), ToUlong(timePercentInt), ToUlong(timePercentDec)); } #endif // OPENTHREAD_CONFIG_RADIO_STATS_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli radio (enable,disable) * @code * radio enable * Done * @endcode * @code * radio disable * Done * @endcode * @cparam radio @ca{enable|disable} * @sa otLinkSetEnabled * @par * Enables or disables the radio. */ if (ProcessEnableDisable(aArgs, otLinkSetEnabled) == OT_ERROR_NONE) { } #if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE /** * @cli radio stats * @code * radio stats * Radio Statistics: * Total Time: 67.756s * Tx Time: 0.022944s (0.03%) * Rx Time: 1.482353s (2.18%) * Sleep Time: 66.251128s (97.77%) * Disabled Time: 0.000080s (0.00%) * Done * @endcode * @par api_copy * #otRadioTimeStatsGet */ else if (aArgs[0] == "stats") { if (aArgs[1].IsEmpty()) { const otRadioTimeStats *radioStats = nullptr; uint64_t totalTimeUs; radioStats = otRadioTimeStatsGet(GetInstancePtr()); totalTimeUs = radioStats->mSleepTime + radioStats->mTxTime + radioStats->mRxTime + radioStats->mDisabledTime; if (totalTimeUs == 0) { OutputLine("Total Time is 0!"); } else { OutputLine("Radio Statistics:"); OutputLine("Total Time: %lu.%03lus", ToUlong(static_cast(totalTimeUs / 1000000)), ToUlong((totalTimeUs % 1000000) / 1000)); OutputRadioStatsTime("Tx", radioStats->mTxTime, totalTimeUs); OutputRadioStatsTime("Rx", radioStats->mRxTime, totalTimeUs); OutputRadioStatsTime("Sleep", radioStats->mSleepTime, totalTimeUs); OutputRadioStatsTime("Disabled", radioStats->mDisabledTime, totalTimeUs); } } /** * @cli radio stats clear * @code * radio stats clear * Done * @endcode * @par api_copy * #otRadioTimeStatsReset */ else if (aArgs[1] == "clear") { otRadioTimeStatsReset(GetInstancePtr()); } } #endif // OPENTHREAD_CONFIG_RADIO_STATS_ENABLE else { error = OT_ERROR_INVALID_COMMAND; } return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; const char *version = otPlatRadioGetVersionString(GetInstancePtr()); VerifyOrExit(version != otGetVersionString(), error = OT_ERROR_NOT_IMPLEMENTED); /** * @cli rcp version * @code * rcp version * OPENTHREAD/20191113-00825-g82053cc9d-dirty; SIMULATION; Jun 4 2020 17:53:16 * Done * @endcode * @par api_copy * #otPlatRadioGetVersionString */ if (aArgs[0] == "version") { OutputLine("%s", version); } else { error = OT_ERROR_INVALID_ARGS; } exit: return error; } /** * @cli region * @code * region * US * Done * @endcode * @par api_copy * #otLinkGetRegion */ template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; uint16_t regionCode; if (aArgs[0].IsEmpty()) { SuccessOrExit(error = otLinkGetRegion(GetInstancePtr(), ®ionCode)); OutputLine("%c%c", regionCode >> 8, regionCode & 0xff); } /** * @cli region (set) * @code * region US * Done * @endcode * @par api_copy * #otLinkSetRegion * @par * Changing this can affect the transmit power limit. */ else { VerifyOrExit(aArgs[0].GetLength() == 2, error = OT_ERROR_INVALID_ARGS); regionCode = static_cast(static_cast(aArgs[0].GetCString()[0]) << 8) + static_cast(aArgs[0].GetCString()[1]); error = otLinkSetRegion(GetInstancePtr(), regionCode); } exit: return error; } #if OPENTHREAD_FTD /** * @cli releaserouterid (routerid) * @code * releaserouterid 16 * Done * @endcode * @cparam releaserouterid [@ca{routerid}] * @par api_copy * #otThreadReleaseRouterId */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessSet(aArgs, otThreadReleaseRouterId); } #endif /** * @cli rloc16 * @code * rloc16 * 0xdead * Done * @endcode * @par api_copy * #otThreadGetRloc16 */ template <> otError Interpreter::Process(Arg aArgs[]) { OT_UNUSED_VARIABLE(aArgs); OutputLine("%04x", otThreadGetRloc16(GetInstancePtr())); return OT_ERROR_NONE; } #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli route * @code * route * 2001:dead:beef:cafe::/64 s med * Done * @endcode * @sa otBorderRouterGetNextRoute * @par * Get the external route list in the local Network Data. */ if (aArgs[0].IsEmpty()) { otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT; otExternalRouteConfig config; while (otBorderRouterGetNextRoute(GetInstancePtr(), &iterator, &config) == OT_ERROR_NONE) { mNetworkData.OutputRoute(config); } } /** * @cli route add * @code * route add 2001:dead:beef:cafe::/64 s med * Done * @endcode * @par * For parameters, use: * * s: Stable flag * * n: NAT64 flag * * prf: Default Router Preference, [high, med, low]. * @cparam route add @ca{prefix} [@ca{sn}] [@ca{high}|@ca{med}|@ca{low}] * @par api_copy * #otExternalRouteConfig * @par * Add a valid external route to the Network Data. */ else if (aArgs[0] == "add") { otExternalRouteConfig config; SuccessOrExit(error = ParseRoute(aArgs + 1, config)); error = otBorderRouterAddRoute(GetInstancePtr(), &config); } /** * @cli route remove * @code * route remove 2001:dead:beef:cafe::/64 * Done * @endcode * @cparam route remove [@ca{prefix}] * @par api_copy * #otBorderRouterRemoveRoute */ else if (aArgs[0] == "remove") { otIp6Prefix prefix; SuccessOrExit(error = aArgs[1].ParseAsIp6Prefix(prefix)); error = otBorderRouterRemoveRoute(GetInstancePtr(), &prefix); } else { error = OT_ERROR_INVALID_COMMAND; } exit: return error; } #endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE #if OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; otRouterInfo routerInfo; uint16_t routerId; bool isTable; /** * @cli router table * @code * router table * | ID | RLOC16 | Next Hop | Path Cost | LQ In | LQ Out | Age | Extended MAC | Link | * +----+--------+----------+-----------+-------+--------+-----+------------------+------+ * | 22 | 0x5800 | 63 | 0 | 0 | 0 | 0 | 0aeb8196c9f61658 | 0 | * | 49 | 0xc400 | 63 | 0 | 3 | 3 | 0 | faa1c03908e2dbf2 | 1 | * Done * @endcode * @sa otThreadGetRouterInfo * @par * Prints a list of routers in a table format. */ isTable = (aArgs[0] == "table"); /** * @cli router list * @code * router list * 8 24 50 * Done * @endcode * @sa otThreadGetRouterInfo * @par * List allocated Router IDs. */ if (isTable || (aArgs[0] == "list")) { uint8_t maxRouterId; if (isTable) { static const char *const kRouterTableTitles[] = { "ID", "RLOC16", "Next Hop", "Path Cost", "LQ In", "LQ Out", "Age", "Extended MAC", "Link", }; static const uint8_t kRouterTableColumnWidths[] = { 4, 8, 10, 11, 7, 8, 5, 18, 6, }; OutputTableHeader(kRouterTableTitles, kRouterTableColumnWidths); } maxRouterId = otThreadGetMaxRouterId(GetInstancePtr()); for (uint8_t i = 0; i <= maxRouterId; i++) { if (otThreadGetRouterInfo(GetInstancePtr(), i, &routerInfo) != OT_ERROR_NONE) { continue; } if (isTable) { OutputFormat("| %2u ", routerInfo.mRouterId); OutputFormat("| 0x%04x ", routerInfo.mRloc16); OutputFormat("| %8u ", routerInfo.mNextHop); OutputFormat("| %9u ", routerInfo.mPathCost); OutputFormat("| %5u ", routerInfo.mLinkQualityIn); OutputFormat("| %6u ", routerInfo.mLinkQualityOut); OutputFormat("| %3u ", routerInfo.mAge); OutputFormat("| "); OutputExtAddress(routerInfo.mExtAddress); OutputLine(" | %4d |", routerInfo.mLinkEstablished); } else { OutputFormat("%u ", i); } } OutputNewLine(); ExitNow(); } /** * @cli router (id) * @code * router 50 * Alloc: 1 * Router ID: 50 * Rloc: c800 * Next Hop: c800 * Link: 1 * Ext Addr: e2b3540590b0fd87 * Cost: 0 * Link Quality In: 3 * Link Quality Out: 3 * Age: 3 * Done * @endcode * @code * router 0xc800 * Alloc: 1 * Router ID: 50 * Rloc: c800 * Next Hop: c800 * Link: 1 * Ext Addr: e2b3540590b0fd87 * Cost: 0 * Link Quality In: 3 * Link Quality Out: 3 * Age: 7 * Done * @endcode * @cparam router [@ca{id}] * @par api_copy * #otThreadGetRouterInfo * @par * Print diagnostic information for a Thread Router. The id may be a Router ID or * an RLOC16. */ SuccessOrExit(error = aArgs[0].ParseAsUint16(routerId)); SuccessOrExit(error = otThreadGetRouterInfo(GetInstancePtr(), routerId, &routerInfo)); OutputLine("Alloc: %d", routerInfo.mAllocated); if (routerInfo.mAllocated) { OutputLine("Router ID: %u", routerInfo.mRouterId); OutputLine("Rloc: %04x", routerInfo.mRloc16); OutputLine("Next Hop: %04x", static_cast(routerInfo.mNextHop) << 10); OutputLine("Link: %d", routerInfo.mLinkEstablished); if (routerInfo.mLinkEstablished) { OutputFormat("Ext Addr: "); OutputExtAddressLine(routerInfo.mExtAddress); OutputLine("Cost: %u", routerInfo.mPathCost); OutputLine("Link Quality In: %u", routerInfo.mLinkQualityIn); OutputLine("Link Quality Out: %u", routerInfo.mLinkQualityOut); OutputLine("Age: %u", routerInfo.mAge); } } exit: return error; } /** * @cli routerdowngradethreshold (get,set) * @code routerdowngradethreshold * 23 * Done * @endcode * @code routerdowngradethreshold 23 * Done * @endcode * @cparam routerdowngradethreshold [@ca{threshold}] * @par * Gets or sets the ROUTER_DOWNGRADE_THRESHOLD value. * @sa otThreadGetRouterDowngradeThreshold * @sa otThreadSetRouterDowngradeThreshold */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessGetSet(aArgs, otThreadGetRouterDowngradeThreshold, otThreadSetRouterDowngradeThreshold); } /** * @cli routereligible * @code * routereligible * Enabled * Done * @endcode * @sa otThreadIsRouterEligible * @par * Indicates whether the router role is enabled or disabled. */ template <> otError Interpreter::Process(Arg aArgs[]) { /** * @cli routereligible (enable,disable) * @code * routereligible enable * Done * @endcode * @code * routereligible disable * Done * @endcode * @cparam routereligible [@ca{enable|disable}] * @sa otThreadSetRouterEligible * @par * Enables or disables the router role. */ return ProcessEnableDisable(aArgs, otThreadIsRouterEligible, otThreadSetRouterEligible); } /** * @cli routerselectionjitter * @code * routerselectionjitter * 120 * Done * @endcode * @code * routerselectionjitter 120 * Done * @endcode * @cparam routerselectionjitter [@ca{jitter}] * @par * Gets or sets the ROUTER_SELECTION_JITTER value. * @sa otThreadGetRouterSelectionJitter * @sa otThreadSetRouterSelectionJitter */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessGetSet(aArgs, otThreadGetRouterSelectionJitter, otThreadSetRouterSelectionJitter); } /** * @cli routerupgradethreshold (get,set) * @code * routerupgradethreshold * 16 * Done * @endcode * @code * routerupgradethreshold 16 * Done * @endcode * @cparam routerupgradethreshold [@ca{threshold}] * @par * Gets or sets the ROUTER_UPGRADE_THRESHOLD value. * @sa otThreadGetRouterUpgradeThreshold * @sa otThreadSetRouterUpgradeThreshold */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessGetSet(aArgs, otThreadGetRouterUpgradeThreshold, otThreadSetRouterUpgradeThreshold); } /** * @cli childrouterlinks (get,set) * @code * childrouterlinks * 16 * Done * @endcode * @code * childrouterlinks 16 * Done * @endcode * @cparam childrouterlinks [@ca{links}] * @par * Gets or sets the MLE_CHILD_ROUTER_LINKS value. * @sa otThreadGetChildRouterLinks * @sa otThreadSetChildRouterLinks */ template <> otError Interpreter::Process(Arg aArgs[]) { return ProcessGetSet(aArgs, otThreadGetChildRouterLinks, otThreadSetChildRouterLinks); } #endif // OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; uint32_t scanChannels = 0; uint16_t scanDuration = 0; bool energyScan = false; if (aArgs[0] == "energy") { energyScan = true; aArgs++; if (!aArgs->IsEmpty()) { SuccessOrExit(error = aArgs->ParseAsUint16(scanDuration)); aArgs++; } } if (!aArgs->IsEmpty()) { uint8_t channel; SuccessOrExit(error = aArgs->ParseAsUint8(channel)); VerifyOrExit(channel < BitSizeOf(scanChannels), error = OT_ERROR_INVALID_ARGS); scanChannels = 1 << channel; } /** * @cli scan energy * @code * scan energy 10 * | Ch | RSSI | * +----+------+ * | 11 | -59 | * | 12 | -62 | * | 13 | -67 | * | 14 | -61 | * | 15 | -87 | * | 16 | -86 | * | 17 | -86 | * | 18 | -52 | * | 19 | -58 | * | 20 | -82 | * | 21 | -76 | * | 22 | -82 | * | 23 | -74 | * | 24 | -81 | * | 25 | -88 | * | 26 | -71 | * Done * @endcode * @code * scan energy 10 20 * | Ch | RSSI | * +----+------+ * | 20 | -82 | * Done * @endcode * @cparam scan energy [@ca{duration}] [@ca{channel}] * @par * Performs an IEEE 802.15.4 energy scan, and displays the time in milliseconds * to use for scanning each channel. All channels are shown unless you specify a certain channel * by using the channel option. * @sa otLinkEnergyScan */ if (energyScan) { static const char *const kEnergyScanTableTitles[] = {"Ch", "RSSI"}; static const uint8_t kEnergyScanTableColumnWidths[] = {4, 6}; OutputTableHeader(kEnergyScanTableTitles, kEnergyScanTableColumnWidths); SuccessOrExit(error = otLinkEnergyScan(GetInstancePtr(), scanChannels, scanDuration, &Interpreter::HandleEnergyScanResult, this)); } /** * @cli scan * @code * scan * | PAN | MAC Address | Ch | dBm | LQI | * +------+------------------+----+-----+-----+ * | ffff | f1d92a82c8d8fe43 | 11 | -20 | 0 | * Done * @endcode * @cparam scan [@ca{channel}] * @par * Performs an active IEEE 802.15.4 scan. The scan covers all channels if no channel is specified; otherwise the * span covers only the channel specified. * @sa otLinkActiveScan */ else { static const char *const kScanTableTitles[] = {"PAN", "MAC Address", "Ch", "dBm", "LQI"}; static const uint8_t kScanTableColumnWidths[] = {6, 18, 4, 5, 5}; OutputTableHeader(kScanTableTitles, kScanTableColumnWidths); SuccessOrExit(error = otLinkActiveScan(GetInstancePtr(), scanChannels, scanDuration, &Interpreter::HandleActiveScanResult, this)); } error = OT_ERROR_PENDING; exit: return error; } void Interpreter::HandleActiveScanResult(otActiveScanResult *aResult, void *aContext) { static_cast(aContext)->HandleActiveScanResult(aResult); } void Interpreter::HandleActiveScanResult(otActiveScanResult *aResult) { if (aResult == nullptr) { OutputResult(OT_ERROR_NONE); ExitNow(); } if (aResult->mDiscover) { OutputFormat("| %-16s ", aResult->mNetworkName.m8); OutputFormat("| "); OutputBytes(aResult->mExtendedPanId.m8); OutputFormat(" "); } OutputFormat("| %04x | ", aResult->mPanId); OutputExtAddress(aResult->mExtAddress); OutputFormat(" | %2u ", aResult->mChannel); OutputFormat("| %3d ", aResult->mRssi); OutputLine("| %3u |", aResult->mLqi); exit: return; } void Interpreter::HandleEnergyScanResult(otEnergyScanResult *aResult, void *aContext) { static_cast(aContext)->HandleEnergyScanResult(aResult); } void Interpreter::HandleEnergyScanResult(otEnergyScanResult *aResult) { if (aResult == nullptr) { OutputResult(OT_ERROR_NONE); ExitNow(); } OutputLine("| %2u | %4d |", aResult->mChannel, aResult->mMaxRssi); exit: return; } /** * @cli singleton * @code * singleton * true * Done * @endcode * @par * Indicates whether a node is the only router on the network. * Returns either `true` or `false`. * @sa otThreadIsSingleton */ template <> otError Interpreter::Process(Arg aArgs[]) { OT_UNUSED_VARIABLE(aArgs); OutputLine(otThreadIsSingleton(GetInstancePtr()) ? "true" : "false"); return OT_ERROR_NONE; } #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; uint16_t port = OT_SNTP_DEFAULT_SERVER_PORT; Ip6::MessageInfo messageInfo; otSntpQuery query; /** * @cli sntp query * @code * sntp query * SNTP response - Unix time: 1540894725 (era: 0) * Done * @endcode * @code * sntp query 64:ff9b::d8ef:2308 * SNTP response - Unix time: 1540898611 (era: 0) * Done * @endcode * @cparam sntp query [@ca{SNTP server IP}] [@ca{SNTP server port}] * @par * Sends an SNTP query to obtain the current unix epoch time (from January 1, 1970). * - SNTP server default IP address: `2001:4860:4806:8::` (Google IPv6 NTP Server) * - SNTP server default port: `123` * @note This command is available only if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE is enabled. * @sa #otSntpClientQuery */ if (aArgs[0] == "query") { VerifyOrExit(!mSntpQueryingInProgress, error = OT_ERROR_BUSY); if (!aArgs[1].IsEmpty()) { SuccessOrExit(error = aArgs[1].ParseAsIp6Address(messageInfo.GetPeerAddr())); } else { // Use IPv6 address of default SNTP server. SuccessOrExit(error = messageInfo.GetPeerAddr().FromString(OT_SNTP_DEFAULT_SERVER_IP)); } if (!aArgs[2].IsEmpty()) { SuccessOrExit(error = aArgs[2].ParseAsUint16(port)); } messageInfo.SetPeerPort(port); query.mMessageInfo = static_cast(&messageInfo); SuccessOrExit(error = otSntpClientQuery(GetInstancePtr(), &query, &Interpreter::HandleSntpResponse, this)); mSntpQueryingInProgress = true; error = OT_ERROR_PENDING; } else { error = OT_ERROR_INVALID_COMMAND; } exit: return error; } void Interpreter::HandleSntpResponse(void *aContext, uint64_t aTime, otError aResult) { static_cast(aContext)->HandleSntpResponse(aTime, aResult); } void Interpreter::HandleSntpResponse(uint64_t aTime, otError aResult) { if (aResult == OT_ERROR_NONE) { // Some Embedded C libraries do not support printing of 64-bit unsigned integers. // To simplify, unix epoch time and era number are printed separately. OutputLine("SNTP response - Unix time: %lu (era: %lu)", ToUlong(static_cast(aTime)), ToUlong(static_cast(aTime >> 32))); } else { OutputLine("SNTP error - %s", otThreadErrorToString(aResult)); } mSntpQueryingInProgress = false; OutputResult(OT_ERROR_NONE); } #endif // OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; if (aArgs[0].IsEmpty()) { #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE OutputLine("client"); #endif #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE OutputLine("server"); #endif ExitNow(); } #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE if (aArgs[0] == "client") { ExitNow(error = mSrpClient.Process(aArgs + 1)); } #endif #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE if (aArgs[0] == "server") { ExitNow(error = mSrpServer.Process(aArgs + 1)); } #endif error = OT_ERROR_INVALID_COMMAND; exit: return error; } #endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli state * @code * state * child * Done * @endcode * @code * state leader * Done * @endcode * @cparam state [@ca{child}|@ca{router}|@ca{leader}|@ca{detached}] * @par * Returns the current role of the Thread device, or changes the role as specified with one of the options. * Possible values returned when inquiring about the device role: * - `child`: The device is currently operating as a Thread child. * - `router`: The device is currently operating as a Thread router. * - `leader`: The device is currently operating as a Thread leader. * - `detached`: The device is not currently participating in a Thread network/partition. * - `disabled`: The Thread stack is currently disabled. * @par * Using one of the options allows you to change the current role of a device, with the exclusion of * changing to or from a `disabled` state. * @sa otThreadGetDeviceRole * @sa otThreadBecomeChild * @sa otThreadBecomeRouter * @sa otThreadBecomeLeader * @sa otThreadBecomeDetached */ if (aArgs[0].IsEmpty()) { OutputLine("%s", otThreadDeviceRoleToString(otThreadGetDeviceRole(GetInstancePtr()))); } else if (aArgs[0] == "detached") { error = otThreadBecomeDetached(GetInstancePtr()); } else if (aArgs[0] == "child") { error = otThreadBecomeChild(GetInstancePtr()); } #if OPENTHREAD_FTD else if (aArgs[0] == "router") { error = otThreadBecomeRouter(GetInstancePtr()); } else if (aArgs[0] == "leader") { error = otThreadBecomeLeader(GetInstancePtr()); } #endif else { error = OT_ERROR_INVALID_ARGS; } return error; } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli thread start * @code * thread start * Done * @endcode * @par * Starts the Thread protocol operation. * @note The interface must be up when running this command. * @sa otThreadSetEnabled */ if (aArgs[0] == "start") { error = otThreadSetEnabled(GetInstancePtr(), true); } /** * @cli thread stop * @code * thread stop * Done * @endcode * @par * Stops the Thread protocol operation. */ else if (aArgs[0] == "stop") { error = otThreadSetEnabled(GetInstancePtr(), false); } /** * @cli thread version * @code thread version * 2 * Done * @endcode * @par api_copy * #otThreadGetVersion */ else if (aArgs[0] == "version") { OutputLine("%u", otThreadGetVersion()); } else { error = OT_ERROR_INVALID_COMMAND; } return error; } #if OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli timeinqueue * @code * timeinqueue * | Min | Max |Msg Count| * +------+------+---------+ * | 0 | 9 | 1537 | * | 10 | 19 | 156 | * | 20 | 29 | 57 | * | 30 | 39 | 108 | * | 40 | 49 | 60 | * | 50 | 59 | 76 | * | 60 | 69 | 88 | * | 70 | 79 | 51 | * | 80 | 89 | 86 | * | 90 | 99 | 45 | * | 100 | 109 | 43 | * | 110 | 119 | 44 | * | 120 | 129 | 38 | * | 130 | 139 | 44 | * | 140 | 149 | 35 | * | 150 | 159 | 41 | * | 160 | 169 | 34 | * | 170 | 179 | 13 | * | 180 | 189 | 24 | * | 190 | 199 | 3 | * | 200 | 209 | 0 | * | 210 | 219 | 0 | * | 220 | 229 | 2 | * | 230 | 239 | 0 | * | 240 | 249 | 0 | * | 250 | 259 | 0 | * | 260 | 269 | 0 | * | 270 | 279 | 0 | * | 280 | 289 | 0 | * | 290 | 299 | 1 | * | 300 | 309 | 0 | * | 310 | 319 | 0 | * | 320 | 329 | 0 | * | 330 | 339 | 0 | * | 340 | 349 | 0 | * | 350 | 359 | 0 | * | 360 | 369 | 0 | * | 370 | 379 | 0 | * | 380 | 389 | 0 | * | 390 | 399 | 0 | * | 400 | 409 | 0 | * | 410 | 419 | 0 | * | 420 | 429 | 0 | * | 430 | 439 | 0 | * | 440 | 449 | 0 | * | 450 | 459 | 0 | * | 460 | 469 | 0 | * | 470 | 479 | 0 | * | 480 | 489 | 0 | * | 490 | inf | 0 | * Done * @endcode * @par api_copy * #otThreadGetTimeInQueueHistogram * @csa{timeinqueue reset} */ if (aArgs[0].IsEmpty()) { static const char *const kTimeInQueueTableTitles[] = {"Min", "Max", "Msg Count"}; static const uint8_t kTimeInQueueTableColumnWidths[] = {6, 6, 9}; uint16_t numBins; uint32_t binInterval; const uint32_t *histogram; OutputTableHeader(kTimeInQueueTableTitles, kTimeInQueueTableColumnWidths); histogram = otThreadGetTimeInQueueHistogram(GetInstancePtr(), &numBins, &binInterval); for (uint16_t index = 0; index < numBins; index++) { OutputFormat("| %4lu | ", ToUlong(index * binInterval)); if (index < numBins - 1) { OutputFormat("%4lu", ToUlong((index + 1) * binInterval - 1)); } else { OutputFormat("%4s", "inf"); } OutputLine(" | %7lu |", ToUlong(histogram[index])); } } /** * @cli timeinqueue max * @code * timeinqueue max * 281 * Done * @endcode * @par api_copy * #otThreadGetMaxTimeInQueue * @csa{timeinqueue reset} */ else if (aArgs[0] == "max") { OutputLine("%lu", ToUlong(otThreadGetMaxTimeInQueue(GetInstancePtr()))); } /** * @cli timeinqueue reset * @code * timeinqueue reset * Done * @endcode * @par api_copy * #otThreadResetTimeInQueueStat */ else if (aArgs[0] == "reset") { otThreadResetTimeInQueueStat(GetInstancePtr()); } else { error = OT_ERROR_INVALID_ARGS; } return error; } #endif // OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mDataset.Process(aArgs); } /** * @cli txpower (get,set) * @code * txpower -10 * Done * @endcode * @code * txpower * -10 dBm * Done * @endcode * @cparam txpower [@ca{txpower}] * @par * Gets (or sets with the use of the optional `txpower` argument) the transmit power in dBm. * @sa otPlatRadioGetTransmitPower * @sa otPlatRadioSetTransmitPower */ template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; int8_t power; if (aArgs[0].IsEmpty()) { SuccessOrExit(error = otPlatRadioGetTransmitPower(GetInstancePtr(), &power)); OutputLine("%d dBm", power); } else { SuccessOrExit(error = aArgs[0].ParseAsInt8(power)); error = otPlatRadioSetTransmitPower(GetInstancePtr(), power); } exit: return error; } /** * @cli debug * @par * Executes a series of CLI commands to gather information about the device and thread network. This is intended for * debugging. * The output will display each executed CLI command preceded by `$`, followed by the corresponding command's * generated output. * The generated output encompasses the following information: * - Version * - Current state * - RLOC16, extended MAC address * - Unicast and multicast IPv6 address list * - Channel * - PAN ID and extended PAN ID * - Network Data * - Partition ID * - Leader Data * @par * If the device is operating as FTD: * - Child and neighbor table * - Router table and next hop info * - Address cache table * - Registered MTD child IPv6 address * - Device properties * @par * If the device supports and acts as an SRP client: * - SRP client state * - SRP client services and host info * @par * If the device supports and acts as an SRP sever: * - SRP server state and address mode * - SRP server registered hosts and services * @par * If the device supports TREL: * - TREL status and peer table * @par * If the device supports and acts as a border router: * - BR state * - BR prefixes (OMR, on-link, NAT64) * - Discovered prefix table */ template <> otError Interpreter::Process(Arg aArgs[]) { static constexpr uint16_t kMaxDebugCommandSize = 30; static const char *const kDebugCommands[] = { "version", "state", "rloc16", "extaddr", "ipaddr", "ipmaddr", "channel", "panid", "extpanid", "netdata show", "netdata show -x", "partitionid", "leaderdata", #if OPENTHREAD_FTD "child table", "childip", "neighbor table", "router table", "nexthop", "eidcache", #if OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE "deviceprops", #endif #endif // OPENTHREAD_FTD #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE "srp client state", "srp client host", "srp client service", "srp client server", #endif #if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE "srp server state", "srp server addrmode", "srp server host", "srp server service", #endif #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE "trel", "trel peers", #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE "br state", "br omrprefix", "br onlinkprefix", "br prefixtable", #if OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE "br nat64prefix", #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_DHCP6_PD_ENABLE "br pd state", "br pd omrprefix", #endif #endif "bufferinfo", }; char commandString[kMaxDebugCommandSize]; OT_UNUSED_VARIABLE(aArgs); mInternalDebugCommand = true; for (const char *debugCommand : kDebugCommands) { strncpy(commandString, debugCommand, sizeof(commandString) - 1); commandString[sizeof(commandString) - 1] = '\0'; OutputLine("$ %s", commandString); ProcessLine(commandString); } mInternalDebugCommand = false; return OT_ERROR_NONE; } #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mTcat.Process(aArgs); } #endif #if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mTcp.Process(aArgs); } #endif template <> otError Interpreter::Process(Arg aArgs[]) { return mUdp.Process(aArgs); } template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli unsecureport add * @code * unsecureport add 1234 * Done * @endcode * @cparam unsecureport add @ca{port} * @par api_copy * #otIp6AddUnsecurePort */ if (aArgs[0] == "add") { error = ProcessSet(aArgs + 1, otIp6AddUnsecurePort); } /** * @cli unsecureport remove * @code * unsecureport remove 1234 * Done * @endcode * @code * unsecureport remove all * Done * @endcode * @cparam unsecureport remove @ca{port}|all * @par * Removes a specified port or all ports from the allowed unsecured port list. * @sa otIp6AddUnsecurePort * @sa otIp6RemoveAllUnsecurePorts */ else if (aArgs[0] == "remove") { if (aArgs[1] == "all") { otIp6RemoveAllUnsecurePorts(GetInstancePtr()); } else { error = ProcessSet(aArgs + 1, otIp6RemoveUnsecurePort); } } /** * @cli unsecure get * @code * unsecure get * 1234 * Done * @endcode * @par * Lists all ports from the allowed unsecured port list. * @sa otIp6GetUnsecurePorts */ else if (aArgs[0] == "get") { const uint16_t *ports; uint8_t numPorts; ports = otIp6GetUnsecurePorts(GetInstancePtr(), &numPorts); if (ports != nullptr) { for (uint8_t i = 0; i < numPorts; i++) { OutputFormat("%u ", ports[i]); } } OutputNewLine(); } else { error = OT_ERROR_INVALID_COMMAND; } return error; } #if OPENTHREAD_CONFIG_UPTIME_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli uptime * @code * uptime * 12:46:35.469 * Done * @endcode * @par api_copy * #otInstanceGetUptimeAsString */ if (aArgs[0].IsEmpty()) { char string[OT_UPTIME_STRING_SIZE]; otInstanceGetUptimeAsString(GetInstancePtr(), string, sizeof(string)); OutputLine("%s", string); } /** * @cli uptime ms * @code * uptime ms * 426238 * Done * @endcode * @par api_copy * #otInstanceGetUptime */ else if (aArgs[0] == "ms") { OutputUint64Line(otInstanceGetUptime(GetInstancePtr())); } else { error = OT_ERROR_INVALID_ARGS; } return error; } #endif #if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD template <> otError Interpreter::Process(Arg aArgs[]) { return mCommissioner.Process(aArgs); } #endif #if OPENTHREAD_CONFIG_JOINER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mJoiner.Process(aArgs); } #endif #if OPENTHREAD_FTD /** * @cli joinerport * @code * joinerport * 1000 * Done * @endcode * @par api_copy * #otThreadGetJoinerUdpPort */ template <> otError Interpreter::Process(Arg aArgs[]) { /** * @cli joinerport (set) * @code * joinerport 1000 * Done * @endcode * @cparam joinerport @ca{udp-port} * @par api_copy * #otThreadSetJoinerUdpPort */ return ProcessGetSet(aArgs, otThreadGetJoinerUdpPort, otThreadSetJoinerUdpPort); } #endif #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { return mMacFilter.Process(aArgs); } #endif template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; if (aArgs[0] == "retries") { /** * @cli mac retries direct (get,set) * @code * mac retries direct * 3 * Done * @endcode * @code * mac retries direct 5 * Done * @endcode * @cparam mac retries direct [@ca{number}] * Use the optional `number` argument to set the number of direct TX retries. * @par * Gets or sets the number of direct TX retries on the MAC layer. * @sa otLinkGetMaxFrameRetriesDirect * @sa otLinkSetMaxFrameRetriesDirect */ if (aArgs[1] == "direct") { error = ProcessGetSet(aArgs + 2, otLinkGetMaxFrameRetriesDirect, otLinkSetMaxFrameRetriesDirect); } #if OPENTHREAD_FTD /** * @cli mac retries indirect (get,set) * @code * mac retries indirect * 3 * Done * @endcode * @code max retries indirect 5 * Done * @endcode * @cparam mac retries indirect [@ca{number}] * Use the optional `number` argument to set the number of indirect Tx retries. * @par * Gets or sets the number of indirect TX retries on the MAC layer. * @sa otLinkGetMaxFrameRetriesIndirect * @sa otLinkSetMaxFrameRetriesIndirect */ else if (aArgs[1] == "indirect") { error = ProcessGetSet(aArgs + 2, otLinkGetMaxFrameRetriesIndirect, otLinkSetMaxFrameRetriesIndirect); } #endif else { error = OT_ERROR_INVALID_ARGS; } } #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE /** * @cli mac send * @code * mac send datarequest * Done * @endcode * @code * mac send emptydata * Done * @endcode * @cparam mac send @ca{datarequest} | @ca{emptydata} * You must choose one of the following two arguments: * - `datarequest`: Enqueues an IEEE 802.15.4 Data Request message for transmission. * - `emptydata`: Instructs the device to send an empty IEEE 802.15.4 data frame. * @par * Instructs an `Rx-Off-When-Idle` device to send a MAC frame to its parent. * This command is for certification, and can only be used when `OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE` is * enabled. * @sa otLinkSendDataRequest * @sa otLinkSendEmptyData */ else if (aArgs[0] == "send") { VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); if (aArgs[1] == "datarequest") { error = otLinkSendDataRequest(GetInstancePtr()); } else if (aArgs[1] == "emptydata") { error = otLinkSendEmptyData(GetInstancePtr()); } else { error = OT_ERROR_INVALID_ARGS; } } #endif else { error = OT_ERROR_INVALID_COMMAND; ExitNow(); // To silence unused `exit` label warning when `REFERENCE_DEVICE_ENABLE` is not enabled. } exit: return error; } /** * @cli trel * @code * trel * Enabled * Done * @endcode * @par api_copy * #otTrelIsEnabled * @note `OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE` is required for all `trel` sub-commands. */ #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; /** * @cli trel (enable,disable) * @code * trel enable * Done * @endcode * @code * trel disable * Done * @endcode * @cparam trel @ca{enable}|@ca{disable} * @par * Enables or disables the TREL radio operation. * @sa otTrelSetEnabled */ if (ProcessEnableDisable(aArgs, otTrelIsEnabled, otTrelSetEnabled) == OT_ERROR_NONE) { } /** * @cli trel filter * @code * trel filter * Disabled * Done * @endcode * @par * Indicates whether TREL filter mode is enabled. * @par * When filter mode is enabled, all Rx and Tx traffic sent through the TREL interface gets silently dropped. * @note This mode is used mostly for testing. * @sa otTrelIsFilterEnabled */ else if (aArgs[0] == "filter") /** * @cli trel filter (enable,disable) * @code * trel filter enable * Done * @endcode * @code * trel filter disable * Done * @endcode * @cparam trel filter @ca{enable}|@ca{disable} * @par * Enables or disables TREL filter mode. * @sa otTrelSetFilterEnabled */ { error = ProcessEnableDisable(aArgs + 1, otTrelIsFilterEnabled, otTrelSetFilterEnabled); } /** * @cli trel peers * @code * trel peers * | No | Ext MAC Address | Ext PAN Id | IPv6 Socket Address | * +-----+------------------+------------------+--------------------------------------------------+ * | 1 | 5e5785ba3a63adb9 | f0d9c001f00d2e43 | [fe80:0:0:0:cc79:2a29:d311:1aea]:9202 | * | 2 | ce792a29d3111aea | dead00beef00cafe | [fe80:0:0:0:5c57:85ba:3a63:adb9]:9203 | * Done * @endcode * @code * trel peers list * 001 ExtAddr:5e5785ba3a63adb9 ExtPanId:f0d9c001f00d2e43 SockAddr:[fe80:0:0:0:cc79:2a29:d311:1aea]:9202 * 002 ExtAddr:ce792a29d3111aea ExtPanId:dead00beef00cafe SockAddr:[fe80:0:0:0:5c57:85ba:3a63:adb9]:9203 * Done * @endcode * @cparam trel peers [@ca{list}] * @par * Gets the TREL peer table in table or list format. * @sa otTrelGetNextPeer */ else if (aArgs[0] == "peers") { uint16_t index = 0; otTrelPeerIterator iterator; const otTrelPeer *peer; bool isTable = true; if (aArgs[1] == "list") { isTable = false; } else { VerifyOrExit(aArgs[1].IsEmpty(), error = OT_ERROR_INVALID_ARGS); } if (isTable) { static const char *const kTrelPeerTableTitles[] = {"No", "Ext MAC Address", "Ext PAN Id", "IPv6 Socket Address"}; static const uint8_t kTrelPeerTableColumnWidths[] = {5, 18, 18, 50}; OutputTableHeader(kTrelPeerTableTitles, kTrelPeerTableColumnWidths); } otTrelInitPeerIterator(GetInstancePtr(), &iterator); while ((peer = otTrelGetNextPeer(GetInstancePtr(), &iterator)) != nullptr) { if (!isTable) { OutputFormat("%03u ExtAddr:", ++index); OutputExtAddress(peer->mExtAddress); OutputFormat(" ExtPanId:"); OutputBytes(peer->mExtPanId.m8); OutputFormat(" SockAddr:"); OutputSockAddrLine(peer->mSockAddr); } else { char string[OT_IP6_SOCK_ADDR_STRING_SIZE]; OutputFormat("| %3u | ", ++index); OutputExtAddress(peer->mExtAddress); OutputFormat(" | "); OutputBytes(peer->mExtPanId.m8); otIp6SockAddrToString(&peer->mSockAddr, string, sizeof(string)); OutputLine(" | %-48s |", string); } } } /** * @cli trel counters * @code * trel counters * Inbound: Packets 32 Bytes 4000 * Outbound: Packets 4 Bytes 320 Failures 1 * Done * @endcode * @par api_copy * #otTrelGetCounters */ else if (aArgs[0] == "counters") { if (aArgs[1].IsEmpty()) { OutputTrelCounters(*otTrelGetCounters(GetInstancePtr())); } /** * @cli trel counters reset * @code * trel counters reset * Done * @endcode * @par api_copy * #otTrelResetCounters */ else if ((aArgs[1] == "reset") && aArgs[2].IsEmpty()) { otTrelResetCounters(GetInstancePtr()); } else { error = OT_ERROR_INVALID_ARGS; } } else { error = OT_ERROR_INVALID_ARGS; } exit: return error; } void Interpreter::OutputTrelCounters(const otTrelCounters &aCounters) { Uint64StringBuffer u64StringBuffer; OutputFormat("Inbound: Packets %s ", Uint64ToString(aCounters.mRxPackets, u64StringBuffer)); OutputLine("Bytes %s", Uint64ToString(aCounters.mRxBytes, u64StringBuffer)); OutputFormat("Outbound: Packets %s ", Uint64ToString(aCounters.mTxPackets, u64StringBuffer)); OutputFormat("Bytes %s ", Uint64ToString(aCounters.mTxBytes, u64StringBuffer)); OutputLine("Failures %s", Uint64ToString(aCounters.mTxFailure, u64StringBuffer)); } #endif template <> otError Interpreter::Process(Arg aArgs[]) { Error error = OT_ERROR_INVALID_ARGS; /** * @cli vendor name * @code * vendor name * nest * Done * @endcode * @par api_copy * #otThreadGetVendorName */ if (aArgs[0] == "name") { aArgs++; #if !OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE error = ProcessGet(aArgs, otThreadGetVendorName); #else /** * @cli vendor name (set) * @code * vendor name nest * Done * @endcode * @par api_copy * #otThreadSetVendorName * @cparam vendor name @ca{name} */ error = ProcessGetSet(aArgs, otThreadGetVendorName, otThreadSetVendorName); #endif } /** * @cli vendor model * @code * vendor model * Hub Max * Done * @endcode * @par api_copy * #otThreadGetVendorModel */ else if (aArgs[0] == "model") { aArgs++; #if !OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE error = ProcessGet(aArgs, otThreadGetVendorModel); #else /** * @cli vendor model (set) * @code * vendor model Hub\ Max * Done * @endcode * @par api_copy * #otThreadSetVendorModel * @cparam vendor model @ca{name} */ error = ProcessGetSet(aArgs, otThreadGetVendorModel, otThreadSetVendorModel); #endif } /** * @cli vendor swversion * @code * vendor swversion * Marble3.5.1 * Done * @endcode * @par api_copy * #otThreadGetVendorSwVersion */ else if (aArgs[0] == "swversion") { aArgs++; #if !OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE error = ProcessGet(aArgs, otThreadGetVendorSwVersion); #else /** * @cli vendor swversion (set) * @code * vendor swversion Marble3.5.1 * Done * @endcode * @par api_copy * #otThreadSetVendorSwVersion * @cparam vendor swversion @ca{version} */ error = ProcessGetSet(aArgs, otThreadGetVendorSwVersion, otThreadSetVendorSwVersion); #endif } /** * @cli vendor appurl * @code * vendor appurl * http://www.example.com * Done * @endcode * @par api_copy * #otThreadGetVendorAppUrl */ else if (aArgs[0] == "appurl") { aArgs++; #if !OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE error = ProcessGet(aArgs, otThreadGetVendorAppUrl); #else /** * @cli vendor appurl (set) * @code * vendor appurl http://www.example.com * Done * @endcode * @par api_copy * #otThreadSetVendorAppUrl * @cparam vendor appurl @ca{url} */ error = ProcessGetSet(aArgs, otThreadGetVendorAppUrl, otThreadSetVendorAppUrl); #endif } return error; } #if OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { static constexpr uint16_t kMaxTlvs = 35; otError error = OT_ERROR_NONE; otIp6Address address; uint8_t tlvTypes[kMaxTlvs]; uint8_t count = 0; SuccessOrExit(error = aArgs[1].ParseAsIp6Address(address)); for (Arg *arg = &aArgs[2]; !arg->IsEmpty(); arg++) { VerifyOrExit(count < sizeof(tlvTypes), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = arg->ParseAsUint8(tlvTypes[count++])); } /** * @cli networkdiagnostic get * @code * networkdiagnostic get fdde:ad00:beef:0:0:ff:fe00:fc00 0 1 6 23 * DIAG_GET.rsp/ans: 00080e336e1c41494e1c01020c000608640b0f674074c503 * Ext Address: 0e336e1c41494e1c * Rloc16: 0x0c00 * Leader Data: * PartitionId: 0x640b0f67 * Weighting: 64 * DataVersion: 116 * StableDataVersion: 197 * LeaderRouterId: 0x03 * EUI64: 18b4300000000004 * Done * @endcode * @code * networkdiagnostic get ff02::1 0 1 * DIAG_GET.rsp/ans: 00080e336e1c41494e1c01020c00 * Ext Address: '0e336e1c41494e1c' * Rloc16: 0x0c00 * Done * DIAG_GET.rsp/ans: 00083efcdb7e3f9eb0f201021800 * Ext Address: 3efcdb7e3f9eb0f2 * Rloc16: 0x1800 * Done * @endcode * @cparam networkdiagnostic get @ca{addr} @ca{type(s)} * For `addr`, a unicast address triggers a `Diagnostic Get`. * A multicast address triggers a `Diagnostic Query`. * TLV values you can specify (separated by a space if you specify more than one TLV): * - `0`: MAC Extended Address TLV * - `1`: Address16 TLV * - `2`: Mode TLV * - `3`: Timeout TLV (the maximum polling time period for SEDs) * - `4`: Connectivity TLV * - `5`: Route64 TLV * - `6`: Leader Data TLV * - `7`: Network Data TLV * - `8`: IPv6 Address List TLV * - `9`: MAC Counters TLV * - `14`: Battery Level TLV * - `15`: Supply Voltage TLV * - `16`: Child Table TLV * - `17`: Channel Pages TLV * - `19`: Max Child Timeout TLV * - `23`: EUI64 TLV * - `24`: Version TLV (version number for the protocols and features) * - `25`: Vendor Name TLV * - `26`: Vendor Model TLV * - `27`: Vendor SW Version TLV * - `28`: Thread Stack Version TLV (version identifier as UTF-8 string for Thread stack codebase/commit/version) * - `29`: Child TLV * - `34`: MLE Counters TLV * - `35`: Vendor App URL TLV * @par * Sends a network diagnostic request to retrieve specified Type Length Values (TLVs) * for the specified addresses(es). * @sa otThreadSendDiagnosticGet */ if (aArgs[0] == "get") { SuccessOrExit(error = otThreadSendDiagnosticGet(GetInstancePtr(), &address, tlvTypes, count, &Interpreter::HandleDiagnosticGetResponse, this)); SetCommandTimeout(kNetworkDiagnosticTimeoutMsecs); error = OT_ERROR_PENDING; } /** * @cli networkdiagnostic reset * @code * networkdiagnostic reset fd00:db8::ff:fe00:0 9 * Done * @endcode * @cparam networkdiagnostic reset @ca{addr} @ca{type(s)} * @par * Sends a network diagnostic request to reset the specified Type Length Values (TLVs) * on the specified address(es). This command only supports the * following TLV values: `9` (MAC Counters TLV) or `34` (MLE * Counters TLV) * @sa otThreadSendDiagnosticReset */ else if (aArgs[0] == "reset") { IgnoreError(otThreadSendDiagnosticReset(GetInstancePtr(), &address, tlvTypes, count)); } else { error = OT_ERROR_INVALID_COMMAND; } exit: return error; } void Interpreter::HandleDiagnosticGetResponse(otError aError, otMessage *aMessage, const otMessageInfo *aMessageInfo, void *aContext) { static_cast(aContext)->HandleDiagnosticGetResponse( aError, aMessage, static_cast(aMessageInfo)); } void Interpreter::HandleDiagnosticGetResponse(otError aError, const otMessage *aMessage, const Ip6::MessageInfo *aMessageInfo) { uint8_t buf[16]; uint16_t bytesToPrint; uint16_t bytesPrinted = 0; uint16_t length; otNetworkDiagTlv diagTlv; otNetworkDiagIterator iterator = OT_NETWORK_DIAGNOSTIC_ITERATOR_INIT; SuccessOrExit(aError); OutputFormat("DIAG_GET.rsp/ans from "); OutputIp6Address(aMessageInfo->mPeerAddr); OutputFormat(": "); length = otMessageGetLength(aMessage) - otMessageGetOffset(aMessage); while (length > 0) { bytesToPrint = Min(length, static_cast(sizeof(buf))); otMessageRead(aMessage, otMessageGetOffset(aMessage) + bytesPrinted, buf, bytesToPrint); OutputBytes(buf, static_cast(bytesToPrint)); length -= bytesToPrint; bytesPrinted += bytesToPrint; } OutputNewLine(); // Output Network Diagnostic TLV values in standard YAML format. while (otThreadGetNextDiagnosticTlv(aMessage, &iterator, &diagTlv) == OT_ERROR_NONE) { switch (diagTlv.mType) { case OT_NETWORK_DIAGNOSTIC_TLV_EXT_ADDRESS: OutputFormat("Ext Address: "); OutputExtAddressLine(diagTlv.mData.mExtAddress); break; case OT_NETWORK_DIAGNOSTIC_TLV_SHORT_ADDRESS: OutputLine("Rloc16: 0x%04x", diagTlv.mData.mAddr16); break; case OT_NETWORK_DIAGNOSTIC_TLV_MODE: OutputLine("Mode:"); OutputMode(kIndentSize, diagTlv.mData.mMode); break; case OT_NETWORK_DIAGNOSTIC_TLV_TIMEOUT: OutputLine("Timeout: %lu", ToUlong(diagTlv.mData.mTimeout)); break; case OT_NETWORK_DIAGNOSTIC_TLV_CONNECTIVITY: OutputLine("Connectivity:"); OutputConnectivity(kIndentSize, diagTlv.mData.mConnectivity); break; case OT_NETWORK_DIAGNOSTIC_TLV_ROUTE: OutputLine("Route:"); OutputRoute(kIndentSize, diagTlv.mData.mRoute); break; case OT_NETWORK_DIAGNOSTIC_TLV_LEADER_DATA: OutputLine("Leader Data:"); OutputLeaderData(kIndentSize, diagTlv.mData.mLeaderData); break; case OT_NETWORK_DIAGNOSTIC_TLV_NETWORK_DATA: OutputFormat("Network Data: "); OutputBytesLine(diagTlv.mData.mNetworkData.m8, diagTlv.mData.mNetworkData.mCount); break; case OT_NETWORK_DIAGNOSTIC_TLV_IP6_ADDR_LIST: OutputLine("IP6 Address List:"); for (uint16_t i = 0; i < diagTlv.mData.mIp6AddrList.mCount; ++i) { OutputFormat(kIndentSize, "- "); OutputIp6AddressLine(diagTlv.mData.mIp6AddrList.mList[i]); } break; case OT_NETWORK_DIAGNOSTIC_TLV_MAC_COUNTERS: OutputLine("MAC Counters:"); OutputNetworkDiagMacCounters(kIndentSize, diagTlv.mData.mMacCounters); break; case OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS: OutputLine("MLE Counters:"); OutputNetworkDiagMleCounters(kIndentSize, diagTlv.mData.mMleCounters); break; case OT_NETWORK_DIAGNOSTIC_TLV_BATTERY_LEVEL: OutputLine("Battery Level: %u%%", diagTlv.mData.mBatteryLevel); break; case OT_NETWORK_DIAGNOSTIC_TLV_SUPPLY_VOLTAGE: OutputLine("Supply Voltage: %umV", diagTlv.mData.mSupplyVoltage); break; case OT_NETWORK_DIAGNOSTIC_TLV_CHILD_TABLE: OutputLine("Child Table:"); for (uint16_t i = 0; i < diagTlv.mData.mChildTable.mCount; ++i) { OutputFormat(kIndentSize, "- "); OutputChildTableEntry(kIndentSize + 2, diagTlv.mData.mChildTable.mTable[i]); } break; case OT_NETWORK_DIAGNOSTIC_TLV_CHANNEL_PAGES: OutputFormat("Channel Pages: '"); OutputBytes(diagTlv.mData.mChannelPages.m8, diagTlv.mData.mChannelPages.mCount); OutputLine("'"); break; case OT_NETWORK_DIAGNOSTIC_TLV_MAX_CHILD_TIMEOUT: OutputLine("Max Child Timeout: %lu", ToUlong(diagTlv.mData.mMaxChildTimeout)); break; case OT_NETWORK_DIAGNOSTIC_TLV_EUI64: OutputFormat("EUI64: "); OutputExtAddressLine(diagTlv.mData.mEui64); break; case OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_NAME: OutputLine("Vendor Name: %s", diagTlv.mData.mVendorName); break; case OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_MODEL: OutputLine("Vendor Model: %s", diagTlv.mData.mVendorModel); break; case OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_SW_VERSION: OutputLine("Vendor SW Version: %s", diagTlv.mData.mVendorSwVersion); break; case OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_APP_URL: OutputLine("Vendor App URL: %s", diagTlv.mData.mVendorAppUrl); break; case OT_NETWORK_DIAGNOSTIC_TLV_THREAD_STACK_VERSION: OutputLine("Thread Stack Version: %s", diagTlv.mData.mThreadStackVersion); break; default: break; } } exit: return; } void Interpreter::OutputMode(uint8_t aIndentSize, const otLinkModeConfig &aMode) { OutputLine(aIndentSize, "RxOnWhenIdle: %d", aMode.mRxOnWhenIdle); OutputLine(aIndentSize, "DeviceType: %d", aMode.mDeviceType); OutputLine(aIndentSize, "NetworkData: %d", aMode.mNetworkData); } void Interpreter::OutputConnectivity(uint8_t aIndentSize, const otNetworkDiagConnectivity &aConnectivity) { OutputLine(aIndentSize, "ParentPriority: %d", aConnectivity.mParentPriority); OutputLine(aIndentSize, "LinkQuality3: %u", aConnectivity.mLinkQuality3); OutputLine(aIndentSize, "LinkQuality2: %u", aConnectivity.mLinkQuality2); OutputLine(aIndentSize, "LinkQuality1: %u", aConnectivity.mLinkQuality1); OutputLine(aIndentSize, "LeaderCost: %u", aConnectivity.mLeaderCost); OutputLine(aIndentSize, "IdSequence: %u", aConnectivity.mIdSequence); OutputLine(aIndentSize, "ActiveRouters: %u", aConnectivity.mActiveRouters); OutputLine(aIndentSize, "SedBufferSize: %u", aConnectivity.mSedBufferSize); OutputLine(aIndentSize, "SedDatagramCount: %u", aConnectivity.mSedDatagramCount); } void Interpreter::OutputRoute(uint8_t aIndentSize, const otNetworkDiagRoute &aRoute) { OutputLine(aIndentSize, "IdSequence: %u", aRoute.mIdSequence); OutputLine(aIndentSize, "RouteData:"); aIndentSize += kIndentSize; for (uint16_t i = 0; i < aRoute.mRouteCount; ++i) { OutputFormat(aIndentSize, "- "); OutputRouteData(aIndentSize + 2, aRoute.mRouteData[i]); } } void Interpreter::OutputRouteData(uint8_t aIndentSize, const otNetworkDiagRouteData &aRouteData) { OutputLine("RouteId: 0x%02x", aRouteData.mRouterId); OutputLine(aIndentSize, "LinkQualityOut: %u", aRouteData.mLinkQualityOut); OutputLine(aIndentSize, "LinkQualityIn: %u", aRouteData.mLinkQualityIn); OutputLine(aIndentSize, "RouteCost: %u", aRouteData.mRouteCost); } void Interpreter::OutputLeaderData(uint8_t aIndentSize, const otLeaderData &aLeaderData) { OutputLine(aIndentSize, "PartitionId: 0x%08lx", ToUlong(aLeaderData.mPartitionId)); OutputLine(aIndentSize, "Weighting: %u", aLeaderData.mWeighting); OutputLine(aIndentSize, "DataVersion: %u", aLeaderData.mDataVersion); OutputLine(aIndentSize, "StableDataVersion: %u", aLeaderData.mStableDataVersion); OutputLine(aIndentSize, "LeaderRouterId: 0x%02x", aLeaderData.mLeaderRouterId); } void Interpreter::OutputNetworkDiagMacCounters(uint8_t aIndentSize, const otNetworkDiagMacCounters &aMacCounters) { struct CounterName { const uint32_t otNetworkDiagMacCounters::*mValuePtr; const char *mName; }; static const CounterName kCounterNames[] = { {&otNetworkDiagMacCounters::mIfInUnknownProtos, "IfInUnknownProtos"}, {&otNetworkDiagMacCounters::mIfInErrors, "IfInErrors"}, {&otNetworkDiagMacCounters::mIfOutErrors, "IfOutErrors"}, {&otNetworkDiagMacCounters::mIfInUcastPkts, "IfInUcastPkts"}, {&otNetworkDiagMacCounters::mIfInBroadcastPkts, "IfInBroadcastPkts"}, {&otNetworkDiagMacCounters::mIfInDiscards, "IfInDiscards"}, {&otNetworkDiagMacCounters::mIfOutUcastPkts, "IfOutUcastPkts"}, {&otNetworkDiagMacCounters::mIfOutBroadcastPkts, "IfOutBroadcastPkts"}, {&otNetworkDiagMacCounters::mIfOutDiscards, "IfOutDiscards"}, }; for (const CounterName &counter : kCounterNames) { OutputLine(aIndentSize, "%s: %lu", counter.mName, ToUlong(aMacCounters.*counter.mValuePtr)); } } void Interpreter::OutputNetworkDiagMleCounters(uint8_t aIndentSize, const otNetworkDiagMleCounters &aMleCounters) { struct CounterName { const uint16_t otNetworkDiagMleCounters::*mValuePtr; const char *mName; }; struct TimeCounterName { const uint64_t otNetworkDiagMleCounters::*mValuePtr; const char *mName; }; static const CounterName kCounterNames[] = { {&otNetworkDiagMleCounters::mDisabledRole, "DisabledRole"}, {&otNetworkDiagMleCounters::mDetachedRole, "DetachedRole"}, {&otNetworkDiagMleCounters::mChildRole, "ChildRole"}, {&otNetworkDiagMleCounters::mRouterRole, "RouterRole"}, {&otNetworkDiagMleCounters::mLeaderRole, "LeaderRole"}, {&otNetworkDiagMleCounters::mAttachAttempts, "AttachAttempts"}, {&otNetworkDiagMleCounters::mPartitionIdChanges, "PartitionIdChanges"}, {&otNetworkDiagMleCounters::mBetterPartitionAttachAttempts, "BetterPartitionAttachAttempts"}, {&otNetworkDiagMleCounters::mParentChanges, "ParentChanges"}, }; static const TimeCounterName kTimeCounterNames[] = { {&otNetworkDiagMleCounters::mTrackedTime, "TrackedTime"}, {&otNetworkDiagMleCounters::mDisabledTime, "DisabledTime"}, {&otNetworkDiagMleCounters::mDetachedTime, "DetachedTime"}, {&otNetworkDiagMleCounters::mChildTime, "ChildTime"}, {&otNetworkDiagMleCounters::mRouterTime, "RouterTime"}, {&otNetworkDiagMleCounters::mLeaderTime, "LeaderTime"}, }; for (const CounterName &counter : kCounterNames) { OutputLine(aIndentSize, "%s: %u", counter.mName, aMleCounters.*counter.mValuePtr); } for (const TimeCounterName &counter : kTimeCounterNames) { OutputFormat("%s: ", counter.mName); OutputUint64Line(aMleCounters.*counter.mValuePtr); } } void Interpreter::OutputChildTableEntry(uint8_t aIndentSize, const otNetworkDiagChildEntry &aChildEntry) { OutputLine("ChildId: 0x%04x", aChildEntry.mChildId); OutputLine(aIndentSize, "Timeout: %u", aChildEntry.mTimeout); OutputLine(aIndentSize, "Link Quality: %u", aChildEntry.mLinkQuality); OutputLine(aIndentSize, "Mode:"); OutputMode(aIndentSize + kIndentSize, aChildEntry.mMode); } #endif // OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE #if OPENTHREAD_FTD void Interpreter::HandleDiscoveryRequest(const otThreadDiscoveryRequestInfo *aInfo, void *aContext) { static_cast(aContext)->HandleDiscoveryRequest(*aInfo); } void Interpreter::HandleDiscoveryRequest(const otThreadDiscoveryRequestInfo &aInfo) { OutputFormat("~ Discovery Request from "); OutputExtAddress(aInfo.mExtAddress); OutputLine(": version=%u,joiner=%d", aInfo.mVersion, aInfo.mIsJoiner); } #endif #if OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK void Interpreter::HandleIp6Receive(otMessage *aMessage, void *aContext) { OT_UNUSED_VARIABLE(aContext); otMessageFree(aMessage); } #endif #if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE template <> otError Interpreter::Process(Arg aArgs[]) { otError error; /** * @cli verhoeff calculate * @code * verhoeff calculate 30731842 * 1 * Done * @endcode * @cparam verhoeff calculate @ca{decimalstring} * @par api_copy * #otVerhoeffChecksumCalculate */ if (aArgs[0] == "calculate") { char checksum; VerifyOrExit(!aArgs[1].IsEmpty() && aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); SuccessOrExit(error = otVerhoeffChecksumCalculate(aArgs[1].GetCString(), &checksum)); OutputLine("%c", checksum); } /** * @cli verhoeff validate * @code * verhoeff validate 307318421 * Done * @endcode * @cparam verhoeff validate @ca{decimalstring} * @par api_copy * #otVerhoeffChecksumValidate */ else if (aArgs[0] == "validate") { VerifyOrExit(!aArgs[1].IsEmpty() && aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); error = otVerhoeffChecksumValidate(aArgs[1].GetCString()); } else { error = OT_ERROR_INVALID_COMMAND; } exit: return error; } #endif // OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE #endif // OPENTHREAD_FTD || OPENTHREAD_MTD void Interpreter::Initialize(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext) { Instance *instance = static_cast(aInstance); Interpreter::sInterpreter = new (&sInterpreterRaw) Interpreter(instance, aCallback, aContext); } void Interpreter::OutputPrompt(void) { #if OPENTHREAD_CONFIG_CLI_PROMPT_ENABLE static const char sPrompt[] = "> "; // The `OutputFormat()` below is adding the prompt which is not // part of any command output, so we set the `EmittingCommandOutput` // flag to false to avoid it being included in the command output // log (under `OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE`). SetEmittingCommandOutput(false); OutputFormat("%s", sPrompt); SetEmittingCommandOutput(true); #endif // OPENTHREAD_CONFIG_CLI_PROMPT_ENABLE } void Interpreter::HandleTimer(Timer &aTimer) { static_cast(static_cast(aTimer).GetContext())->HandleTimer(); } void Interpreter::HandleTimer(void) { #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE if (mLocateInProgress) { mLocateInProgress = false; OutputResult(OT_ERROR_RESPONSE_TIMEOUT); } else #endif { OutputResult(OT_ERROR_NONE); } } void Interpreter::SetCommandTimeout(uint32_t aTimeoutMilli) { OT_ASSERT(mCommandIsPending); mTimer.Start(aTimeoutMilli); } otError Interpreter::ProcessCommand(Arg aArgs[]) { #define CmdEntry(aCommandString) \ { \ aCommandString, &Interpreter::Process \ } static constexpr Command kCommands[] = { #if OPENTHREAD_FTD || OPENTHREAD_MTD #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE CmdEntry("ba"), #endif #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) CmdEntry("bbr"), #endif #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE CmdEntry("br"), #endif CmdEntry("bufferinfo"), CmdEntry("ccathreshold"), #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("ccm"), #endif CmdEntry("channel"), #if OPENTHREAD_FTD CmdEntry("child"), CmdEntry("childip"), CmdEntry("childmax"), CmdEntry("childrouterlinks"), #endif CmdEntry("childsupervision"), CmdEntry("childtimeout"), #if OPENTHREAD_CONFIG_COAP_API_ENABLE CmdEntry("coap"), #endif #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE CmdEntry("coaps"), #endif #if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE CmdEntry("coex"), #endif #if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE && OPENTHREAD_FTD CmdEntry("commissioner"), #endif #if OPENTHREAD_FTD CmdEntry("contextreusedelay"), #endif CmdEntry("counters"), #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE CmdEntry("csl"), #endif CmdEntry("dataset"), CmdEntry("debug"), #if OPENTHREAD_FTD CmdEntry("delaytimermin"), #endif CmdEntry("detach"), #endif // OPENTHREAD_FTD || OPENTHREAD_MTD #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE CmdEntry("deviceprops"), #endif #if OPENTHREAD_CONFIG_DIAG_ENABLE CmdEntry("diag"), #endif #if OPENTHREAD_FTD || OPENTHREAD_MTD CmdEntry("discover"), #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE || OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE || \ OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("dns"), #endif #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) CmdEntry("domainname"), #endif #if OPENTHREAD_CONFIG_DUA_ENABLE CmdEntry("dua"), #endif #if OPENTHREAD_FTD CmdEntry("eidcache"), #endif CmdEntry("eui64"), CmdEntry("extaddr"), CmdEntry("extpanid"), CmdEntry("factoryreset"), #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("fake"), #endif CmdEntry("fem"), #endif // OPENTHREAD_FTD || OPENTHREAD_MTD #if OPENTHREAD_FTD || OPENTHREAD_MTD #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE CmdEntry("history"), #endif CmdEntry("ifconfig"), CmdEntry("instanceid"), CmdEntry("ipaddr"), CmdEntry("ipmaddr"), #if OPENTHREAD_CONFIG_JOINER_ENABLE CmdEntry("joiner"), #endif #if OPENTHREAD_FTD CmdEntry("joinerport"), #endif CmdEntry("keysequence"), CmdEntry("leaderdata"), #if OPENTHREAD_FTD CmdEntry("leaderweight"), #endif #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE CmdEntry("linkmetrics"), #if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE CmdEntry("linkmetricsmgr"), #endif #endif #if OPENTHREAD_CONFIG_TMF_ANYCAST_LOCATOR_ENABLE CmdEntry("locate"), #endif CmdEntry("log"), CmdEntry("mac"), #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE CmdEntry("macfilter"), #endif #if OPENTHREAD_CONFIG_MULTICAST_DNS_ENABLE && OPENTHREAD_CONFIG_MULTICAST_DNS_PUBLIC_API_ENABLE CmdEntry("mdns"), #endif #if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD CmdEntry("meshdiag"), #endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("mleadvimax"), #endif #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("mliid"), #endif #if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE CmdEntry("mlr"), #endif CmdEntry("mode"), CmdEntry("multiradio"), #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE || OPENTHREAD_CONFIG_NAT64_BORDER_ROUTING_ENABLE CmdEntry("nat64"), #endif #if OPENTHREAD_FTD CmdEntry("neighbor"), #endif CmdEntry("netdata"), CmdEntry("netstat"), #if OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE CmdEntry("networkdiagnostic"), #endif #if OPENTHREAD_FTD CmdEntry("networkidtimeout"), #endif CmdEntry("networkkey"), #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE CmdEntry("networkkeyref"), #endif CmdEntry("networkname"), #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE CmdEntry("networktime"), #endif #if OPENTHREAD_FTD CmdEntry("nexthop"), #endif CmdEntry("panid"), CmdEntry("parent"), #if OPENTHREAD_FTD CmdEntry("parentpriority"), CmdEntry("partitionid"), #endif #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE CmdEntry("ping"), #endif CmdEntry("platform"), CmdEntry("pollperiod"), #if OPENTHREAD_FTD CmdEntry("preferrouterid"), #endif #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE CmdEntry("prefix"), #endif CmdEntry("promiscuous"), #if OPENTHREAD_FTD CmdEntry("pskc"), #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE CmdEntry("pskcref"), #endif #endif #if OPENTHREAD_CONFIG_RADIO_STATS_ENABLE CmdEntry("radio"), #endif #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE && OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE CmdEntry("radiofilter"), #endif CmdEntry("rcp"), CmdEntry("region"), #if OPENTHREAD_FTD CmdEntry("releaserouterid"), #endif #endif // OPENTHREAD_FTD || OPENTHREAD_MTD CmdEntry("reset"), #if OPENTHREAD_FTD || OPENTHREAD_MTD CmdEntry("rloc16"), #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE CmdEntry("route"), #endif #if OPENTHREAD_FTD CmdEntry("router"), CmdEntry("routerdowngradethreshold"), CmdEntry("routereligible"), #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("routeridrange"), #endif CmdEntry("routerselectionjitter"), CmdEntry("routerupgradethreshold"), #endif CmdEntry("scan"), #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE CmdEntry("service"), #endif CmdEntry("singleton"), #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE CmdEntry("sntp"), #endif #if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE CmdEntry("srp"), #endif CmdEntry("state"), #if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE && OPENTHREAD_CONFIG_CLI_BLE_SECURE_ENABLE CmdEntry("tcat"), #endif #if OPENTHREAD_CONFIG_TCP_ENABLE && OPENTHREAD_CONFIG_CLI_TCP_ENABLE CmdEntry("tcp"), #endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("test"), #endif CmdEntry("thread"), #if OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE CmdEntry("timeinqueue"), #endif #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE CmdEntry("trel"), #endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE CmdEntry("tvcheck"), #endif CmdEntry("txpower"), CmdEntry("udp"), CmdEntry("unsecureport"), #if OPENTHREAD_CONFIG_UPTIME_ENABLE CmdEntry("uptime"), #endif CmdEntry("vendor"), #if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE CmdEntry("verhoeff"), #endif #endif // OPENTHREAD_FTD || OPENTHREAD_MTD CmdEntry("version"), }; #undef CmdEntry static_assert(BinarySearch::IsSorted(kCommands), "Command Table is not sorted"); otError error = OT_ERROR_NONE; const Command *command = BinarySearch::Find(aArgs[0].GetCString(), kCommands); if (command != nullptr) { error = (this->*command->mHandler)(aArgs + 1); } else if (aArgs[0] == "help") { OutputCommandTable(kCommands); for (const UserCommandsEntry &entry : mUserCommands) { for (uint8_t i = 0; i < entry.mLength; i++) { OutputLine("%s", entry.mCommands[i].mName); } } } else { error = ProcessUserCommands(aArgs); } return error; } extern "C" void otCliInit(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext) { Interpreter::Initialize(aInstance, aCallback, aContext); #if OPENTHREAD_CONFIG_CLI_VENDOR_COMMANDS_ENABLE && OPENTHREAD_CONFIG_CLI_MAX_USER_CMD_ENTRIES > 1 otCliVendorSetUserCommands(); #endif } extern "C" void otCliInputLine(char *aBuf) { Interpreter::GetInterpreter().ProcessLine(aBuf); } extern "C" otError otCliSetUserCommands(const otCliCommand *aUserCommands, uint8_t aLength, void *aContext) { return Interpreter::GetInterpreter().SetUserCommands(aUserCommands, aLength, aContext); } extern "C" void otCliOutputBytes(const uint8_t *aBytes, uint8_t aLength) { Interpreter::GetInterpreter().OutputBytes(aBytes, aLength); } extern "C" void otCliOutputFormat(const char *aFmt, ...) { va_list aAp; va_start(aAp, aFmt); Interpreter::GetInterpreter().OutputFormatV(aFmt, aAp); va_end(aAp); } extern "C" void otCliAppendResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); } extern "C" void otCliPlatLogv(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, va_list aArgs) { OT_UNUSED_VARIABLE(aLogLevel); OT_UNUSED_VARIABLE(aLogRegion); VerifyOrExit(Interpreter::IsInitialized()); // CLI output is being used for logging, so we set the flag // `EmittingCommandOutput` to false indicate this. Interpreter::GetInterpreter().SetEmittingCommandOutput(false); Interpreter::GetInterpreter().OutputFormatV(aFormat, aArgs); Interpreter::GetInterpreter().OutputNewLine(); Interpreter::GetInterpreter().SetEmittingCommandOutput(true); exit: return; } } // namespace Cli } // namespace ot