From d94e8d71cc3158051fd4858b0f7454ab9f2c20be Mon Sep 17 00:00:00 2001 From: Song Guo Date: Sun, 28 Jan 2024 15:21:54 +0000 Subject: [PATCH] [chip-tool] support queue command when sending command to ICD device --- examples/chip-tool/BUILD.gn | 2 + .../commands/clusters/ModelCommand.cpp | 11 +- .../commands/clusters/ModelCommand.h | 7 ++ .../chip-tool/commands/common/CHIPCommand.cpp | 2 +- .../chip-tool/commands/common/CHIPCommand.h | 7 +- examples/chip-tool/commands/common/Command.h | 5 + .../chip-tool/commands/common/Commands.cpp | 106 +++++++++++++++++- examples/chip-tool/commands/common/Commands.h | 18 +++ .../commands/icd/CheckInDelegate.cpp | 29 +++++ .../chip-tool/commands/icd/CheckInDelegate.h | 35 ++++++ .../interactive/InteractiveCommands.cpp | 67 +++++++++++ .../interactive/InteractiveCommands.h | 40 ++++++- 12 files changed, 322 insertions(+), 7 deletions(-) create mode 100644 examples/chip-tool/commands/icd/CheckInDelegate.cpp create mode 100644 examples/chip-tool/commands/icd/CheckInDelegate.h diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn index a27c3cd65716bd..4bf27f4d351add 100644 --- a/examples/chip-tool/BUILD.gn +++ b/examples/chip-tool/BUILD.gn @@ -71,6 +71,8 @@ static_library("chip-tool-utils") { "commands/discover/DiscoverCommand.cpp", "commands/discover/DiscoverCommissionablesCommand.cpp", "commands/discover/DiscoverCommissionersCommand.cpp", + "commands/icd/CheckInDelegate.cpp", + "commands/icd/CheckInDelegate.h", "commands/icd/ICDCommand.cpp", "commands/icd/ICDCommand.h", "commands/pairing/OpenCommissioningWindowCommand.cpp", diff --git a/examples/chip-tool/commands/clusters/ModelCommand.cpp b/examples/chip-tool/commands/clusters/ModelCommand.cpp index c908f9fa71b548..9a2eb256774243 100644 --- a/examples/chip-tool/commands/clusters/ModelCommand.cpp +++ b/examples/chip-tool/commands/clusters/ModelCommand.cpp @@ -84,12 +84,17 @@ void ModelCommand::CheckPeerICDType() return; } + mIsPeerLIT.SetValue(IsDestinationRegisteredLIT()); +} + +bool ModelCommand::IsDestinationRegisteredLIT() +{ app::ICDClientInfo info; auto destinationPeerId = chip::ScopedNodeId(mDestinationId, CurrentCommissioner().GetFabricIndex()); auto iter = CHIPCommand::sICDClientStorage.IterateICDClientInfo(); if (iter == nullptr) { - return; + return false; } app::DefaultICDClientStorage::ICDClientInfoIteratorWrapper clientInfoIteratorWrapper(iter); @@ -98,8 +103,8 @@ void ModelCommand::CheckPeerICDType() if (ScopedNodeId(info.peer_node.GetNodeId(), info.peer_node.GetFabricIndex()) == destinationPeerId) { ChipLogProgress(chipTool, "Peer is a registered LIT ICD."); - mIsPeerLIT.SetValue(true); - return; + return true; } } + return false; } diff --git a/examples/chip-tool/commands/clusters/ModelCommand.h b/examples/chip-tool/commands/clusters/ModelCommand.h index 9561932c9b74e0..c7ca0b9e6e7a4b 100644 --- a/examples/chip-tool/commands/clusters/ModelCommand.h +++ b/examples/chip-tool/commands/clusters/ModelCommand.h @@ -69,6 +69,13 @@ class ModelCommand : public CHIPCommand void Shutdown() override; + chip::ScopedNodeId GetDestination() override + { + return chip::ScopedNodeId(mDestinationId, CurrentCommissioner().GetFabricIndex()); + } + + bool IsDestinationRegisteredLIT() override; + protected: bool IsPeerLIT() { return mIsPeerLIT.ValueOr(false); } diff --git a/examples/chip-tool/commands/common/CHIPCommand.cpp b/examples/chip-tool/commands/common/CHIPCommand.cpp index 78abff12ada208..17c6a075b775a3 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.cpp +++ b/examples/chip-tool/commands/common/CHIPCommand.cpp @@ -50,7 +50,7 @@ chip::Credentials::GroupDataProviderImpl CHIPCommand::sGroupDataProvider{ kMaxGr // All fabrics share the same ICD client storage. chip::app::DefaultICDClientStorage CHIPCommand::sICDClientStorage; chip::Crypto::RawKeySessionKeystore CHIPCommand::sSessionKeystore; -chip::app::DefaultCheckInDelegate CHIPCommand::sCheckInDelegate; +CheckInDelegate CHIPCommand::sCheckInDelegate; chip::app::CheckInHandler CHIPCommand::sCheckInHandler; namespace { diff --git a/examples/chip-tool/commands/common/CHIPCommand.h b/examples/chip-tool/commands/common/CHIPCommand.h index 55817b5213e589..d09bf2c6d5582a 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.h +++ b/examples/chip-tool/commands/common/CHIPCommand.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -117,6 +118,10 @@ class CHIPCommand : public Command StopWaiting(); } + void AddExtraCheckInDelegate(chip::app::CheckInDelegate * delegate) { sCheckInDelegate.AddExtraDelegate(delegate); } + + void RemoveExtraCheckInDelegate(chip::app::CheckInDelegate * delegate) { sCheckInDelegate.RemoveExtraDelegate(delegate); } + protected: // Will be called in a setting in which it's safe to touch the CHIP // stack. The rules for Run() are as follows: @@ -164,7 +169,7 @@ class CHIPCommand : public Command static chip::Credentials::GroupDataProviderImpl sGroupDataProvider; static chip::app::DefaultICDClientStorage sICDClientStorage; - static chip::app::DefaultCheckInDelegate sCheckInDelegate; + static CheckInDelegate sCheckInDelegate; static chip::app::CheckInHandler sCheckInHandler; CredentialIssuerCommands * mCredIssuerCmds; diff --git a/examples/chip-tool/commands/common/Command.h b/examples/chip-tool/commands/common/Command.h index 26b80e54b6b322..b4d6adfbc1e3a3 100644 --- a/examples/chip-tool/commands/common/Command.h +++ b/examples/chip-tool/commands/common/Command.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -277,6 +278,10 @@ class Command const chip::Optional & GetStorageDirectory() const { return mStorageDirectory; } + virtual chip::ScopedNodeId GetDestination() { return chip::ScopedNodeId(); } + + virtual bool IsDestinationRegisteredLIT() { return false; } + protected: // mStorageDirectory lives here so we can just set it in RunAsInteractive. chip::Optional mStorageDirectory; diff --git a/examples/chip-tool/commands/common/Commands.cpp b/examples/chip-tool/commands/common/Commands.cpp index 6a815f98e896ec..b92e34db195f9e 100644 --- a/examples/chip-tool/commands/common/Commands.cpp +++ b/examples/chip-tool/commands/common/Commands.cpp @@ -36,6 +36,7 @@ namespace { char kInteractiveModeName[] = ""; +char kQueuedCommandModeName[] = "(queue)"; constexpr size_t kInteractiveModeArgumentsMaxLength = 32; constexpr char kOptionalArgumentPrefix[] = "--"; constexpr char kJsonClusterKey[] = "cluster"; @@ -222,6 +223,7 @@ CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive, const chip::Optional & interactiveStorageDirectory, bool interactiveAdvertiseOperational) { Command * command = nullptr; + bool queueCommand = false; if (argc <= 1) { @@ -230,6 +232,27 @@ CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive, return CHIP_ERROR_INVALID_ARGUMENT; } + if (strncmp(argv[1], "dumpstate", 9) == 0) + { + DumpState(); + return CHIP_NO_ERROR; + } + if (strncmp(argv[1], "queue", 5) == 0) + { + if (!interactive) + { + ChipLogError(chipTool, "Commands can be queued in interactive mode only."); + return CHIP_ERROR_INVALID_ARGUMENT; + } + queueCommand = true; + argc--; + argv++; + } + if (strncmp(argv[0], kQueuedCommandModeName, strlen(kQueuedCommandModeName)) == 0) + { + ChipLogProgress(chipTool, "Running previously queued command."); + } + auto commandSetIter = GetCommandSet(argv[1]); if (commandSetIter == mCommandSets.end()) { @@ -307,7 +330,32 @@ CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive, if (interactive) { - return command->RunAsInteractive(interactiveStorageDirectory, interactiveAdvertiseOperational); + if (!queueCommand) + { + return command->RunAsInteractive(interactiveStorageDirectory, interactiveAdvertiseOperational); + } + + chip::ScopedNodeId destination = command->GetDestination(); + if (destination.GetNodeId() == chip::kUndefinedNodeId) + { + ChipLogError(chipTool, "The command cannot be queued!"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + if (!command->IsDestinationRegisteredLIT()) + { + ChipLogError(chipTool, "The destination is not a registered LIT device."); + return CHIP_ERROR_INVALID_ARGUMENT; + } + std::vector queuedCommand; + for (int i = 1; i < argc; i++) + { + queuedCommand.push_back(std::string(argv[i])); + } + + mQueuedCommands[destination].emplace_back(queuedCommand); + ChipLogError(chipTool, "The command is queued for node %" PRIu32 ":" ChipLogFormatX64, + static_cast(destination.GetFabricIndex()), ChipLogValueX64(destination.GetNodeId())); + return CHIP_NO_ERROR; } // Now that the command is initialized, get our storage from it as needed @@ -331,6 +379,62 @@ CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive, return command->Run(); } +void Commands::DumpState() const +{ + fprintf(stderr, ".mQueuedCommands = \n"); + for (const auto & queue : mQueuedCommands) + { + auto peer = queue.first; + fprintf(stderr, " [%" PRIu32 ":" ChipLogFormatX64 "] = \n", static_cast(peer.GetFabricIndex()), + ChipLogValueX64(peer.GetNodeId())); + for (const auto & command : queue.second) + { + std::string commandStr; + for (auto & arg : command) + { + commandStr += arg; + commandStr += " "; + } + fprintf(stderr, " %s\n", commandStr.c_str()); + } + } +} + +void Commands::RunAllQueuedCommandsForNode(chip::ScopedNodeId nodeId, const chip::Optional & interactiveStorageDirectory, + bool interactiveAdvertiseOperational) +{ + auto commands = mQueuedCommands[nodeId]; + mQueuedCommands[nodeId].clear(); + + for (auto && command : commands) + { + int argc = 0; + char * argv[kInteractiveModeArgumentsMaxLength] = {}; + argv[argc++] = kQueuedCommandModeName; + + std::string commandStr; + for (auto & arg : command) + { + argv[argc] = new char[arg.size() + 1]; + strcpy(argv[argc++], arg.c_str()); + commandStr += arg; + commandStr += " "; + } + + ChipLogProgress(chipTool, "Execute previously queued command: %s", commandStr.c_str()); + auto err = RunCommand(argc, argv, true, interactiveStorageDirectory, interactiveAdvertiseOperational); + + // Do not delete arg[0] + for (auto i = 1; i < argc; i++) + { + delete[] argv[i]; + } + + ChipLogError(chipTool, "Command execution complete!"); + ChipLogError(chipTool, "Execution result: %s", chip::ErrorStr(err)); + } +} + Commands::CommandSetMap::iterator Commands::GetCommandSet(std::string commandSetName) { for (auto & commandSet : mCommandSets) diff --git a/examples/chip-tool/commands/common/Commands.h b/examples/chip-tool/commands/common/Commands.h index bb6c62dcb035fe..3ad61774221e2a 100644 --- a/examples/chip-tool/commands/common/Commands.h +++ b/examples/chip-tool/commands/common/Commands.h @@ -23,7 +23,10 @@ #endif // CONFIG_USE_LOCAL_STORAGE #include "Command.h" +#include +#include #include +#include class Commands { @@ -44,7 +47,19 @@ class Commands int Run(int argc, char ** argv); int RunInteractive(const char * command, const chip::Optional & storageDirectory, bool advertiseOperational); + void RunAllQueuedCommandsForNode(chip::ScopedNodeId nodeId, const chip::Optional & interactiveStorageDirectory, + bool interactiveAdvertiseOperational); + private: + struct ScopedNodeIdComparer + { + bool operator()(const chip::ScopedNodeId & lhs, const chip::ScopedNodeId & rhs) const + { + return lhs.GetFabricIndex() == rhs.GetFabricIndex() ? lhs.GetNodeId() < rhs.GetNodeId() + : lhs.GetFabricIndex() < rhs.GetFabricIndex(); + } + }; + struct CommandSet { CommandsVector commands; @@ -66,6 +81,8 @@ class Commands bool IsEventCommand(std::string commandName) const; bool IsGlobalCommand(std::string commandName) const; + void DumpState() const; + void ShowCommandSets(std::string executable); static void ShowCommandSetOverview(std::string commandSetName, const CommandSet & commandSet); void ShowCommandSet(std::string executable, std::string commandSetName, CommandsVector & commands, const char * helpText); @@ -83,6 +100,7 @@ class Commands void Register(const char * commandSetName, commands_list commandsList, const char * helpText, bool isCluster); CommandSetMap mCommandSets; + std::map>, ScopedNodeIdComparer> mQueuedCommands; #ifdef CONFIG_USE_LOCAL_STORAGE PersistentStorage mStorage; #endif // CONFIG_USE_LOCAL_STORAGE diff --git a/examples/chip-tool/commands/icd/CheckInDelegate.cpp b/examples/chip-tool/commands/icd/CheckInDelegate.cpp new file mode 100644 index 00000000000000..98a9f36a32f849 --- /dev/null +++ b/examples/chip-tool/commands/icd/CheckInDelegate.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "CheckInDelegate.h" + +void CheckInDelegate::OnCheckInComplete(const chip::app::ICDClientInfo & clientInfo) +{ + chip::app::DefaultCheckInDelegate::OnCheckInComplete(clientInfo); + + if (mExtraDelegate != nullptr) + { + mExtraDelegate->OnCheckInComplete(clientInfo); + } +} diff --git a/examples/chip-tool/commands/icd/CheckInDelegate.h b/examples/chip-tool/commands/icd/CheckInDelegate.h new file mode 100644 index 00000000000000..8d15193b8a1825 --- /dev/null +++ b/examples/chip-tool/commands/icd/CheckInDelegate.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include +#include + +class CheckInDelegate : public chip::app::DefaultCheckInDelegate +{ +public: + void OnCheckInComplete(const chip::app::ICDClientInfo & clientInfo) override; + + void AddExtraDelegate(chip::app::CheckInDelegate * delegate) { mExtraDelegate = delegate; } + + void RemoveExtraDelegate(chip::app::CheckInDelegate * delegate) { mExtraDelegate = nullptr; } + +private: + chip::app::CheckInDelegate * mExtraDelegate; +}; diff --git a/examples/chip-tool/commands/interactive/InteractiveCommands.cpp b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp index 284c145cfe0569..fc0cd97a635dc6 100644 --- a/examples/chip-tool/commands/interactive/InteractiveCommands.cpp +++ b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp @@ -306,13 +306,19 @@ std::string InteractiveStartCommand::GetHistoryFilePath() const CHIP_ERROR InteractiveServerCommand::RunCommand() { + CHIPCommand::AddExtraCheckInDelegate(this); // Logs needs to be redirected in order to refresh the screen appropriately when something // is dumped to stdout while the user is typing a command. chip::Logging::SetLogRedirectCallback(InteractiveServerLoggingCallback); + StartCommandExecutorThread(); + RemoteDataModelLogger::SetDelegate(this); ReturnErrorOnFailure(mWebSocketServer.Run(mPort, this)); + CHIPCommand::RemoveExtraCheckInDelegate(this); + JoinCommandExecutorThread(); + gInteractiveServerResult.Reset(); SetCommandExitStatus(CHIP_NO_ERROR); return CHIP_NO_ERROR; @@ -355,12 +361,16 @@ CHIP_ERROR InteractiveServerCommand::LogJSON(const char * json) CHIP_ERROR InteractiveStartCommand::RunCommand() { + CHIPCommand::AddExtraCheckInDelegate(this); + read_history(GetHistoryFilePath().c_str()); // Logs needs to be redirected in order to refresh the screen appropriately when something // is dumped to stdout while the user is typing a command. chip::Logging::SetLogRedirectCallback(LoggingCallback); + StartCommandExecutorThread(); + char * command = nullptr; int status; while (true) @@ -378,10 +388,67 @@ CHIP_ERROR InteractiveStartCommand::RunCommand() command = nullptr; } + JoinCommandExecutorThread(); + CHIPCommand::RemoveExtraCheckInDelegate(this); SetCommandExitStatus(CHIP_NO_ERROR); return CHIP_NO_ERROR; } +void InteractiveCommand::StartCommandExecutorThread() +{ + commandExecutorThread = std::thread(&InteractiveCommand::CommandExecutor, this); +} + +void InteractiveCommand::JoinCommandExecutorThread() +{ + { + std::lock_guard lock(commandExecutorMutex); + commandExecutorQueue.push(CommandExecutorTask(false)); + commandExecutorCv.notify_all(); + } + commandExecutorThread.join(); +} + +void InteractiveCommand::CommandExecutor() +{ + std::vector tasks; + while (true) + { + // Hold the lock as short as possible. Since OnCheckInComplete also uses the lock. + // If the lock covers execution call it will be a deadlock when a check-in message arrived during executing the commands. + { + std::unique_lock lock(commandExecutorMutex); + commandExecutorCv.wait(lock, [this]() { return !commandExecutorQueue.empty(); }); + + while (!commandExecutorQueue.empty()) + { + tasks.emplace_back(commandExecutorQueue.front()); + commandExecutorQueue.pop(); + } + } + + for (const auto & task : tasks) + { + switch (task.kind) + { + case CommandExecutorTask::Kind::STOP: + return; + case CommandExecutorTask::Kind::ON_CHECK_IN_COMPLETE: + mHandler->RunAllQueuedCommandsForNode(task.payload.Get(), GetStorageDirectory(), + NeedsOperationalAdvertising()); + break; + } + } + } +} + +void InteractiveCommand::OnCheckInComplete(const chip::app::ICDClientInfo & clientInfo) +{ + std::lock_guard lock(commandExecutorMutex); + commandExecutorQueue.push(CommandExecutorTask(clientInfo.peer_node)); + commandExecutorCv.notify_all(); +} + bool InteractiveCommand::ParseCommand(char * command, int * status) { if (strcmp(command, kInteractiveModeStopCommand) == 0) diff --git a/examples/chip-tool/commands/interactive/InteractiveCommands.h b/examples/chip-tool/commands/interactive/InteractiveCommands.h index 88a33dcb26c96f..150cc6bcdab7e6 100644 --- a/examples/chip-tool/commands/interactive/InteractiveCommands.h +++ b/examples/chip-tool/commands/interactive/InteractiveCommands.h @@ -18,15 +18,22 @@ #pragma once +#include +#include +#include +#include + #include "../clusters/DataModelLogger.h" #include "../common/CHIPCommand.h" #include "../common/Commands.h" +#include "../icd/CheckInDelegate.h" +#include #include class Commands; -class InteractiveCommand : public CHIPCommand +class InteractiveCommand : public CHIPCommand, public CheckInDelegate { public: InteractiveCommand(const char * name, Commands * commandsHandler, const char * helpText, @@ -44,9 +51,40 @@ class InteractiveCommand : public CHIPCommand bool ParseCommand(char * command, int * status); + void OnCheckInComplete(const chip::app::ICDClientInfo & clientInfo) override; + +protected: + void StartCommandExecutorThread(); + void JoinCommandExecutorThread(); + private: + void CommandExecutor(); + Commands * mHandler = nullptr; chip::Optional mAdvertiseOperational; + + class CommandExecutorTask + { + public: + chip::Variant payload; + + enum class Kind + { + ON_CHECK_IN_COMPLETE, + STOP, + } kind; + + CommandExecutorTask(chip::ScopedNodeId nodeId) : kind(Kind::ON_CHECK_IN_COMPLETE) + { + payload.Set(nodeId); + } + CommandExecutorTask(bool stop) : kind(Kind::STOP) { payload.Set(stop); } + }; + + std::mutex commandExecutorMutex; + std::condition_variable commandExecutorCv; + std::queue commandExecutorQueue; + std::thread commandExecutorThread; }; class InteractiveStartCommand : public InteractiveCommand