diff --git a/.github/workflows/examples-linux-arm.yaml b/.github/workflows/examples-linux-arm.yaml index 670e583372032d..e8d54c89dbce20 100644 --- a/.github/workflows/examples-linux-arm.yaml +++ b/.github/workflows/examples-linux-arm.yaml @@ -69,7 +69,7 @@ jobs: ./scripts/run_in_build_env.sh \ "./scripts/build/build_examples.py \ --target linux-arm64-all-clusters \ - --target linux-arm64-chip-tool-ipv6only \ + --target linux-arm64-chip-tool-no-interactive-ipv6only \ --target linux-arm64-door-lock \ --target linux-arm64-minmdns \ --target linux-arm64-thermostat-no-ble \ @@ -79,8 +79,8 @@ jobs: timeout-minutes: 5 run: | .environment/pigweed-venv/bin/python3 scripts/tools/memory/gh_sizes.py \ - linux arm64 chip-tool-ipv6only \ - out/linux-arm64-chip-tool-ipv6only/chip-tool \ + linux arm64 chip-tool-no-interactive-ipv6only \ + out/linux-arm64-chip-tool-no-interactive-ipv6only/chip-tool \ /tmp/bloat_reports/ - name: Bloat report - thermostat timeout-minutes: 5 diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn index 03f97a667ad84a..2ee00a016ce0c2 100644 --- a/examples/chip-tool/BUILD.gn +++ b/examples/chip-tool/BUILD.gn @@ -23,6 +23,7 @@ assert(chip_build_tools) declare_args() { # Use a separate eventloop for CHIP tasks config_use_separate_eventloop = true + config_use_interactive_mode = true } config("config") { @@ -32,9 +33,16 @@ config("config") { "${chip_root}/src/lib", ] - defines = [ "CONFIG_USE_SEPARATE_EVENTLOOP=${config_use_separate_eventloop}" ] + defines = [ + "CONFIG_USE_SEPARATE_EVENTLOOP=${config_use_separate_eventloop}", + "CONFIG_USE_INTERACTIVE_MODE=${config_use_interactive_mode}", + ] cflags = [ "-Wconversion" ] + + if (config_use_interactive_mode) { + libs = [ "readline" ] + } } static_library("chip-tool-utils") { @@ -67,6 +75,10 @@ static_library("chip-tool-utils") { "config/PersistentStorage.cpp", ] + if (config_use_interactive_mode) { + sources += [ "commands/interactive/InteractiveCommands.cpp" ] + } + public_deps = [ "${chip_root}/src/app/server", "${chip_root}/src/app/tests/suites/commands/commissioner", diff --git a/examples/chip-tool/commands/clusters/ModelCommand.cpp b/examples/chip-tool/commands/clusters/ModelCommand.cpp index 593d306a2930ca..46db9931292052 100644 --- a/examples/chip-tool/commands/clusters/ModelCommand.cpp +++ b/examples/chip-tool/commands/clusters/ModelCommand.cpp @@ -56,3 +56,10 @@ void ModelCommand::OnDeviceConnectionFailureFn(void * context, PeerId peerId, CH VerifyOrReturn(command != nullptr, ChipLogError(chipTool, "OnDeviceConnectionFailureFn: context is null")); command->SetCommandExitStatus(err); } + +void ModelCommand::Shutdown() +{ + ResetArguments(); + mOnDeviceConnectedCallback.Cancel(); + mOnDeviceConnectionFailureCallback.Cancel(); +} diff --git a/examples/chip-tool/commands/clusters/ModelCommand.h b/examples/chip-tool/commands/clusters/ModelCommand.h index a3cdf003a13e78..6066d51c27cda2 100644 --- a/examples/chip-tool/commands/clusters/ModelCommand.h +++ b/examples/chip-tool/commands/clusters/ModelCommand.h @@ -47,11 +47,7 @@ class ModelCommand : public CHIPCommand virtual CHIP_ERROR SendGroupCommand(chip::GroupId groupId, chip::FabricIndex fabricIndex) { return CHIP_ERROR_BAD_REQUEST; }; - void Shutdown() override - { - mOnDeviceConnectedCallback.Cancel(); - mOnDeviceConnectionFailureCallback.Cancel(); - } + void Shutdown() override; protected: chip::Optional mTimeout; diff --git a/examples/chip-tool/commands/clusters/ReportCommand.h b/examples/chip-tool/commands/clusters/ReportCommand.h index 124034c07b426a..86ea8014d345d8 100644 --- a/examples/chip-tool/commands/clusters/ReportCommand.h +++ b/examples/chip-tool/commands/clusters/ReportCommand.h @@ -376,7 +376,6 @@ class SubscribeAttribute : public ReportCommand AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); - AddArgument("wait", 0, 1, &mWait); AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); ReportCommand::AddArguments(); } @@ -388,7 +387,6 @@ class SubscribeAttribute : public ReportCommand AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); - AddArgument("wait", 0, 1, &mWait); AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); ReportCommand::AddArguments(); } @@ -402,7 +400,6 @@ class SubscribeAttribute : public ReportCommand AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); AddArgument("data-version", 0, UINT32_MAX, &mDataVersion); - AddArgument("wait", 0, 1, &mWait); AddArgument("fabric-filtered", 0, 1, &mFabricFiltered); ReportCommand::AddArguments(); } @@ -416,26 +413,23 @@ class SubscribeAttribute : public ReportCommand mDataVersion); } - chip::System::Clock::Timeout GetWaitDuration() const override - { - return mWait ? chip::System::Clock::Seconds16(UINT16_MAX) : ReportCommand::GetWaitDuration(); - } + chip::System::Clock::Timeout GetWaitDuration() const override { return ReportCommand::GetWaitDuration(); } void OnAttributeSubscription() override { - if (!mWait) - { - // The ReadClient instance can not be released directly into the OnAttributeSubscription - // callback since it happens to be called by ReadClient itself which is doing additional - // work after that. - chip::DeviceLayer::PlatformMgr().ScheduleWork( - [](intptr_t arg) { - auto * command = reinterpret_cast(arg); + // The ReadClient instance can not be released directly into the OnAttributeSubscription + // callback since it happens to be called by ReadClient itself which is doing additional + // work after that. + chip::DeviceLayer::PlatformMgr().ScheduleWork( + [](intptr_t arg) { + auto * command = reinterpret_cast(arg); + if (!command->IsInteractive()) + { command->mReadClient.reset(); - command->SetCommandExitStatus(CHIP_NO_ERROR); - }, - reinterpret_cast(this)); - } + } + command->SetCommandExitStatus(CHIP_NO_ERROR); + }, + reinterpret_cast(this)); } private: @@ -445,7 +439,6 @@ class SubscribeAttribute : public ReportCommand uint16_t mMinInterval; uint16_t mMaxInterval; chip::Optional> mDataVersion; - bool mWait; }; class ReadEvent : public ReportCommand @@ -496,7 +489,6 @@ class SubscribeEvent : public ReportCommand AddArgument("event-id", 0, UINT32_MAX, &mEventIds); AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); - AddArgument("wait", 0, 1, &mWait); ReportCommand::AddArguments(); } @@ -506,7 +498,6 @@ class SubscribeEvent : public ReportCommand AddArgument("event-id", 0, UINT32_MAX, &mEventIds); AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); - AddArgument("wait", 0, 1, &mWait); ReportCommand::AddArguments(); } @@ -518,7 +509,6 @@ class SubscribeEvent : public ReportCommand AddArgument("attr-name", eventName); AddArgument("min-interval", 0, UINT16_MAX, &mMinInterval); AddArgument("max-interval", 0, UINT16_MAX, &mMaxInterval); - AddArgument("wait", 0, 1, &mWait); ReportCommand::AddArguments(); } @@ -530,26 +520,23 @@ class SubscribeEvent : public ReportCommand chip::app::ReadClient::InteractionType::Subscribe, mMinInterval, mMaxInterval); } - chip::System::Clock::Timeout GetWaitDuration() const override - { - return mWait ? chip::System::Clock::Seconds16(UINT16_MAX) : ReportCommand::GetWaitDuration(); - } + chip::System::Clock::Timeout GetWaitDuration() const override { return ReportCommand::GetWaitDuration(); } void OnEventSubscription() override { - if (!mWait) - { - // The ReadClient instance can not be released directly into the OnEventSubscription - // callback since it happens to be called by ReadClient itself which is doing additional - // work after that. - chip::DeviceLayer::PlatformMgr().ScheduleWork( - [](intptr_t arg) { - auto * command = reinterpret_cast(arg); + // The ReadClient instance can not be released directly into the OnEventSubscription + // callback since it happens to be called by ReadClient itself which is doing additional + // work after that. + chip::DeviceLayer::PlatformMgr().ScheduleWork( + [](intptr_t arg) { + auto * command = reinterpret_cast(arg); + if (!command->IsInteractive()) + { command->mReadClient.reset(); - command->SetCommandExitStatus(CHIP_NO_ERROR); - }, - reinterpret_cast(this)); - } + } + command->SetCommandExitStatus(CHIP_NO_ERROR); + }, + reinterpret_cast(this)); } private: @@ -558,5 +545,4 @@ class SubscribeEvent : public ReportCommand uint16_t mMinInterval; uint16_t mMaxInterval; - bool mWait; }; diff --git a/examples/chip-tool/commands/clusters/SubscriptionsCommands.h b/examples/chip-tool/commands/clusters/SubscriptionsCommands.h new file mode 100644 index 00000000000000..641cd3aa72b250 --- /dev/null +++ b/examples/chip-tool/commands/clusters/SubscriptionsCommands.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 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 + +class ShutdownSubscription : public CHIPCommand +{ +public: + ShutdownSubscription(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("shutdown-subscription", credsIssuerConfig) + { + AddArgument("subscription-id", 0, UINT64_MAX, &mSubscriptionId); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + CHIP_ERROR err = chip::app::InteractionModelEngine::GetInstance()->ShutdownSubscription(mSubscriptionId); + SetCommandExitStatus(err); + return CHIP_NO_ERROR; + } + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } + +private: + uint64_t mSubscriptionId; +}; + +class ShutdownSubscriptions : public CHIPCommand +{ +public: + ShutdownSubscriptions(CredentialIssuerCommands * credsIssuerConfig) : CHIPCommand("shutdown-subscriptions", credsIssuerConfig) + { + AddArgument("fabric-index", 0, UINT64_MAX, &mFabricIndex); + AddArgument("node-id", 0, UINT64_MAX, &mNodeId); + } + + /////////// CHIPCommand Interface ///////// + CHIP_ERROR RunCommand() override + { + CHIP_ERROR err = chip::app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(mFabricIndex, mNodeId); + SetCommandExitStatus(err); + return CHIP_NO_ERROR; + } + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } + +private: + chip::FabricIndex mFabricIndex; + chip::NodeId mNodeId; +}; + +void registerClusterSubscriptions(Commands & commands, CredentialIssuerCommands * credsIssuerConfig) +{ + const char * clusterName = "Subscriptions"; + + commands_list clusterCommands = { + make_unique(credsIssuerConfig), // + make_unique(credsIssuerConfig), // + }; + + commands.Register(clusterName, clusterCommands); +} diff --git a/examples/chip-tool/commands/common/CHIPCommand.cpp b/examples/chip-tool/commands/common/CHIPCommand.cpp index 51475fb1e06c3c..7a4741fceb2b7d 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.cpp +++ b/examples/chip-tool/commands/common/CHIPCommand.cpp @@ -30,6 +30,8 @@ #include "TraceHandlers.h" #endif // CHIP_CONFIG_TRANSPORT_TRACE_ENABLED +std::map> CHIPCommand::mCommissioners; + using DeviceControllerFactory = chip::Controller::DeviceControllerFactory; constexpr chip::FabricId kIdentityNullFabricId = chip::kUndefinedFabricId; @@ -53,8 +55,13 @@ const chip::Credentials::AttestationTrustStore * GetTestFileAttestationTrustStor } } // namespace -CHIP_ERROR CHIPCommand::Run() +CHIP_ERROR CHIPCommand::MaybeSetUpStack() { + if (IsInteractive()) + { + return CHIP_NO_ERROR; + } + StartTracing(); #if CHIP_DEVICE_LAYER_TARGET_LINUX && CHIP_DEVICE_CONFIG_ENABLE_CHIPOBLE @@ -107,10 +114,16 @@ CHIP_ERROR CHIPCommand::Run() ReturnLogErrorOnFailure(chip::GroupTesting::InitData(fabric->GetFabricIndex(), compressed_fabric_id_span)); } } - chip::DeviceLayer::PlatformMgr().ScheduleWork(RunQueuedCommand, reinterpret_cast(this)); - CHIP_ERROR err = StartWaiting(GetWaitDuration()); - Shutdown(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR CHIPCommand::MaybeTearDownStack() +{ + if (IsInteractive()) + { + return CHIP_NO_ERROR; + } // // We can call DeviceController::Shutdown() safely without grabbing the stack lock @@ -123,6 +136,20 @@ CHIP_ERROR CHIPCommand::Run() ReturnLogErrorOnFailure(ShutdownCommissioner(kIdentityGamma)); StopTracing(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR CHIPCommand::Run() +{ + ReturnErrorOnFailure(MaybeSetUpStack()); + + CHIP_ERROR err = StartWaiting(GetWaitDuration()); + + Shutdown(); + + ReturnErrorOnFailure(MaybeTearDownStack()); + return err; } @@ -291,17 +318,38 @@ CHIP_ERROR CHIPCommand::StartWaiting(chip::System::Clock::Timeout duration) { #if CONFIG_USE_SEPARATE_EVENTLOOP // ServiceEvents() calls StartEventLoopTask(), which is paired with the StopEventLoopTask() below. - ReturnLogErrorOnFailure(DeviceControllerFactory::GetInstance().ServiceEvents()); - auto waitingUntil = std::chrono::system_clock::now() + std::chrono::duration_cast(duration); + if (!IsInteractive()) { - std::unique_lock lk(cvWaitingForResponseMutex); - if (!cvWaitingForResponse.wait_until(lk, waitingUntil, [this]() { return !this->mWaitingForResponse; })) + ReturnLogErrorOnFailure(DeviceControllerFactory::GetInstance().ServiceEvents()); + } + + if (duration.count() == 0) + { + mCommandExitStatus = RunCommand(); + } + else + { + { + std::lock_guard lk(cvWaitingForResponseMutex); + mWaitingForResponse = true; + } + + chip::DeviceLayer::PlatformMgr().ScheduleWork(RunQueuedCommand, reinterpret_cast(this)); + auto waitingUntil = std::chrono::system_clock::now() + std::chrono::duration_cast(duration); { - mCommandExitStatus = CHIP_ERROR_TIMEOUT; + std::unique_lock lk(cvWaitingForResponseMutex); + if (!cvWaitingForResponse.wait_until(lk, waitingUntil, [this]() { return !this->mWaitingForResponse; })) + { + mCommandExitStatus = CHIP_ERROR_TIMEOUT; + } } } - LogErrorOnFailure(chip::DeviceLayer::PlatformMgr().StopEventLoopTask()); + if (!IsInteractive()) + { + LogErrorOnFailure(chip::DeviceLayer::PlatformMgr().StopEventLoopTask()); + } #else + chip::DeviceLayer::PlatformMgr().ScheduleWork(RunQueuedCommand, reinterpret_cast(this)); ReturnLogErrorOnFailure(chip::DeviceLayer::SystemLayer().StartTimer(duration, OnResponseTimeout, this)); chip::DeviceLayer::PlatformMgr().RunEventLoop(); #endif // CONFIG_USE_SEPARATE_EVENTLOOP diff --git a/examples/chip-tool/commands/common/CHIPCommand.h b/examples/chip-tool/commands/common/CHIPCommand.h index d562b3f6c12147..670d7c6c7851d0 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.h +++ b/examples/chip-tool/commands/common/CHIPCommand.h @@ -102,11 +102,14 @@ class CHIPCommand : public Command ChipDeviceCommissioner & CurrentCommissioner(); private: + CHIP_ERROR MaybeSetUpStack(); + CHIP_ERROR MaybeTearDownStack(); + CHIP_ERROR InitializeCommissioner(std::string key, chip::FabricId fabricId, const chip::Credentials::AttestationTrustStore * trustStore); CHIP_ERROR ShutdownCommissioner(std::string key); chip::FabricId CurrentCommissionerId(); - std::map> mCommissioners; + static std::map> mCommissioners; chip::Optional mCommissionerName; chip::Optional mBleAdapterId; chip::Optional mPaaTrustStorePath; diff --git a/examples/chip-tool/commands/common/Command.cpp b/examples/chip-tool/commands/common/Command.cpp index 7753234d7905de..944645f2cfbd0d 100644 --- a/examples/chip-tool/commands/common/Command.cpp +++ b/examples/chip-tool/commands/common/Command.cpp @@ -721,3 +721,31 @@ size_t Command::AddArgumentToList(Argument && argument) VerifyOrDie(false); return 0; } + +void Command::ResetArguments() +{ + for (size_t i = 0; i < mArgs.size(); i++) + { + const Argument arg = mArgs[i]; + const ArgumentType type = arg.type; + const uint8_t flags = arg.flags; + if (type == ArgumentType::Vector16 && flags != Argument::kOptional) + { + auto vectorArgument = static_cast *>(arg.value); + vectorArgument->clear(); + } + else if (type == ArgumentType::Vector32 && flags != Argument::kOptional) + { + auto vectorArgument = static_cast *>(arg.value); + vectorArgument->clear(); + } + else if (type == ArgumentType::Vector32 && flags == Argument::kOptional) + { + auto optionalArgument = static_cast> *>(arg.value); + if (optionalArgument->HasValue()) + { + optionalArgument->Value().clear(); + } + } + } +} diff --git a/examples/chip-tool/commands/common/Command.h b/examples/chip-tool/commands/common/Command.h index a9c208770c387e..b0a256816e26c0 100644 --- a/examples/chip-tool/commands/common/Command.h +++ b/examples/chip-tool/commands/common/Command.h @@ -224,8 +224,18 @@ class Command return AddArgument(name, min, max, reinterpret_cast(value), flags | Argument::kNullable); } + void ResetArguments(); + virtual CHIP_ERROR Run() = 0; + bool IsInteractive() { return mIsInteractive; } + + CHIP_ERROR RunAsInteractive() + { + mIsInteractive = true; + return Run(); + } + private: bool InitArgument(size_t argIndex, char * argValue); size_t AddArgument(const char * name, int64_t min, uint64_t max, void * out, ArgumentType type, uint8_t flags); @@ -237,6 +247,7 @@ class Command */ size_t AddArgumentToList(Argument && argument); - const char * mName = nullptr; + const char * mName = nullptr; + bool mIsInteractive = false; std::vector mArgs; }; diff --git a/examples/chip-tool/commands/common/Commands.cpp b/examples/chip-tool/commands/common/Commands.cpp index 0e444768ac5739..fcc7137a240e63 100644 --- a/examples/chip-tool/commands/common/Commands.cpp +++ b/examples/chip-tool/commands/common/Commands.cpp @@ -53,7 +53,13 @@ int Commands::Run(int argc, char ** argv) return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; } -CHIP_ERROR Commands::RunCommand(int argc, char ** argv) +int Commands::RunInteractive(int argc, char ** argv) +{ + CHIP_ERROR err = RunCommand(argc, argv, true); + return (err == CHIP_NO_ERROR) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +CHIP_ERROR Commands::RunCommand(int argc, char ** argv, bool interactive) { std::map::iterator cluster; Command * command = nullptr; @@ -131,7 +137,7 @@ CHIP_ERROR Commands::RunCommand(int argc, char ** argv) return CHIP_ERROR_INVALID_ARGUMENT; } - return command->Run(); + return interactive ? command->RunAsInteractive() : command->Run(); } std::map::iterator Commands::GetCluster(std::string clusterName) diff --git a/examples/chip-tool/commands/common/Commands.h b/examples/chip-tool/commands/common/Commands.h index 8a2239c1e13271..e70a942779c9e1 100644 --- a/examples/chip-tool/commands/common/Commands.h +++ b/examples/chip-tool/commands/common/Commands.h @@ -29,9 +29,10 @@ class Commands void Register(const char * clusterName, commands_list commandsList); int Run(int argc, char ** argv); + int RunInteractive(int argc, char ** argv); private: - CHIP_ERROR RunCommand(int argc, char ** argv); + CHIP_ERROR RunCommand(int argc, char ** argv, bool interactive = false); std::map::iterator GetCluster(std::string clusterName); Command * GetCommand(CommandsVector & commands, std::string commandName); diff --git a/examples/chip-tool/commands/interactive/Commands.h b/examples/chip-tool/commands/interactive/Commands.h new file mode 100644 index 00000000000000..19feb20f163999 --- /dev/null +++ b/examples/chip-tool/commands/interactive/Commands.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 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 "../common/CHIPCommand.h" +#include "../common/Commands.h" + +#include "InteractiveCommands.h" + +void registerCommandsInteractive(Commands & commands, CredentialIssuerCommands * credsIssuerConfig) +{ + const char * clusterName = "interactive"; + + commands_list clusterCommands = { +#if CONFIG_USE_INTERACTIVE_MODE + make_unique(&commands, credsIssuerConfig), +#endif // CONFIG_USE_INTERACTIVE_MODE + }; + + commands.Register(clusterName, clusterCommands); +} diff --git a/examples/chip-tool/commands/interactive/InteractiveCommands.cpp b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp new file mode 100644 index 00000000000000..35030eeee7943a --- /dev/null +++ b/examples/chip-tool/commands/interactive/InteractiveCommands.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2022 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 "InteractiveCommands.h" + +#include +#include + +char kInteractiveModeName[] = ""; +constexpr const char * kInteractiveModePrompt = ">>> "; +constexpr uint8_t kInteractiveModeArgumentsMaxLength = 32; +constexpr const char * kInteractiveModeHistoryFilePath = "/tmp/chip_tool_history"; +constexpr const char * kInteractiveModeStopCommand = "quit()"; + +namespace { + +bool gIsCommandRunning = false; + +void ClearLine() +{ + printf("\r\x1B[0J"); // Move cursor to the beginning of the line and clear from cursor to end of the screen +} + +void ENFORCE_FORMAT(3, 0) LoggingCallback(const char * module, uint8_t category, const char * msg, va_list args) +{ + ClearLine(); + chip::Logging::Platform::LogV(module, category, msg, args); + ClearLine(); + + if (gIsCommandRunning == false) + { + rl_forced_update_display(); + } +} +} // namespace + +char * GetCommand(char * command) +{ + if (command != nullptr) + { + free(command); + command = nullptr; + } + + command = readline(kInteractiveModePrompt); + + // Do not save empty lines + if (command != nullptr && *command) + { + add_history(command); + write_history(kInteractiveModeHistoryFilePath); + } + + return command; +} + +CHIP_ERROR InteractiveStartCommand::RunCommand() +{ + read_history(kInteractiveModeHistoryFilePath); + + // 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); + + char * command = nullptr; + while (true) + { + command = GetCommand(command); + if (command != nullptr && !ParseCommand(command)) + { + break; + } + } + + SetCommandExitStatus(CHIP_NO_ERROR); + return CHIP_NO_ERROR; +} + +bool InteractiveStartCommand::ParseCommand(char * command) +{ + if (strcmp(command, kInteractiveModeStopCommand) == 0) + { + return false; + } + + char * args[kInteractiveModeArgumentsMaxLength]; + args[0] = kInteractiveModeName; + int argsCount = 1; + + char * token = strtok(command, " "); + while (token != nullptr) + { + if (argsCount == kInteractiveModeArgumentsMaxLength) + { + gIsCommandRunning = true; + ChipLogError(chipTool, "Too many arguments. Ignoring."); + gIsCommandRunning = false; + return true; + } + + args[argsCount++] = token; + token = strtok(NULL, " "); + } + + ClearLine(); + gIsCommandRunning = true; + mHandler->RunInteractive(argsCount, args); + gIsCommandRunning = false; + + return true; +} diff --git a/examples/chip-tool/commands/interactive/InteractiveCommands.h b/examples/chip-tool/commands/interactive/InteractiveCommands.h new file mode 100644 index 00000000000000..dc3582d621189b --- /dev/null +++ b/examples/chip-tool/commands/interactive/InteractiveCommands.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 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 "../common/CHIPCommand.h" +#include "../common/Commands.h" + +#include "InteractiveCommands.h" + +class Commands; + +class InteractiveStartCommand : public CHIPCommand +{ +public: + InteractiveStartCommand(Commands * commandsHandler, CredentialIssuerCommands * credsIssuerConfig) : + CHIPCommand("start", credsIssuerConfig), mHandler(commandsHandler) + {} + + CHIP_ERROR RunCommand() override; + + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(0); } + +private: + bool ParseCommand(char * command); + Commands * mHandler = nullptr; +}; diff --git a/examples/chip-tool/main.cpp b/examples/chip-tool/main.cpp index 10e6b7440fb09c..8ebc770775db60 100644 --- a/examples/chip-tool/main.cpp +++ b/examples/chip-tool/main.cpp @@ -21,6 +21,7 @@ #include "commands/discover/Commands.h" #include "commands/group/Commands.h" +#include "commands/interactive/Commands.h" #include "commands/pairing/Commands.h" #include "commands/payload/Commands.h" @@ -35,6 +36,7 @@ int main(int argc, char * argv[]) ExampleCredentialIssuerCommands credIssuerCommands; Commands commands; registerCommandsDiscover(commands, &credIssuerCommands); + registerCommandsInteractive(commands, &credIssuerCommands); registerCommandsPayload(commands); registerCommandsPairing(commands, &credIssuerCommands); registerCommandsTests(commands, &credIssuerCommands); diff --git a/examples/chip-tool/templates/commands.zapt b/examples/chip-tool/templates/commands.zapt index ef2af18f2dac58..7212d60112014d 100644 --- a/examples/chip-tool/templates/commands.zapt +++ b/examples/chip-tool/templates/commands.zapt @@ -10,6 +10,7 @@ #include #include #include +#include #include {{> clusters_header}} @@ -180,4 +181,5 @@ void registerClusters(Commands & commands, CredentialIssuerCommands * credsIssue {{#zcl_clusters}} registerCluster{{asUpperCamelCase name}}(commands, credsIssuerConfig); {{/zcl_clusters}} + registerClusterSubscriptions(commands, credsIssuerConfig); } diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py index fdfcc8a8a071b6..d1e8f96dcb4a94 100644 --- a/scripts/build/build/targets.py +++ b/scripts/build/build/targets.py @@ -250,6 +250,8 @@ def HostTargets(): ['-all-clusters', '-chip-tool']), test_group=True), builder.AppendVariant(name="same-event-loop", validator=AcceptNameWithSubstrings( ['-chip-tool']), separate_event_loop=False), + builder.AppendVariant(name="no-interactive", validator=AcceptNameWithSubstrings( + ['-chip-tool']), interactive_mode=False), builder.AppendVariant(name="ipv6only", enable_ipv4=False), builder.AppendVariant(name="no-ble", enable_ble=False), builder.AppendVariant(name="no-wifi", enable_wifi=False), diff --git a/scripts/build/builders/host.py b/scripts/build/builders/host.py index a97b44c0df873c..07957aa2d36f82 100644 --- a/scripts/build/builders/host.py +++ b/scripts/build/builders/host.py @@ -155,7 +155,7 @@ class HostBuilder(GnBuilder): def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, enable_ipv4=True, enable_ble=True, enable_wifi=True, use_tsan=False, use_asan=False, separate_event_loop=True, - test_group=False, use_libfuzzer=False, use_clang=False, + test_group=False, use_libfuzzer=False, use_clang=False, interactive_mode=True, use_platform_mdns=False): super(HostBuilder, self).__init__( root=os.path.join(root, 'examples', app.ExamplePath()), @@ -183,6 +183,9 @@ def __init__(self, root, runner, app: HostApp, board=HostBoard.NATIVE, enable_ip if not separate_event_loop: self.extra_gn_options.append('config_use_separate_eventloop=false') + if not interactive_mode: + self.extra_gn_options.append('config_use_interactive_mode=false') + if test_group: self.extra_gn_options.append( 'chip_enable_group_messaging_tests=true') diff --git a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h index 538689964b83b9..ad09748c291a63 100644 --- a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h +++ b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h @@ -27,6 +27,7 @@ #include #include #include +#include #include /*----------------------------------------------------------------------------*\ @@ -26421,4 +26422,5 @@ void registerClusters(Commands & commands, CredentialIssuerCommands * credsIssue registerClusterApplianceEventsAndAlert(commands, credsIssuerConfig); registerClusterApplianceStatistics(commands, credsIssuerConfig); registerClusterElectricalMeasurement(commands, credsIssuerConfig); + registerClusterSubscriptions(commands, credsIssuerConfig); }