diff --git a/examples/placeholder/linux/AppOptions.cpp b/examples/placeholder/linux/AppOptions.cpp index c780c314194ef9..115f17a4782ed2 100644 --- a/examples/placeholder/linux/AppOptions.cpp +++ b/examples/placeholder/linux/AppOptions.cpp @@ -23,8 +23,12 @@ using chip::ArgParser::OptionSet; using chip::ArgParser::PrintArgError; constexpr uint16_t kOptionDacProviderFilePath = 0xFF01; +constexpr uint16_t kOptionInteractiveMode = 0xFF02; +constexpr uint16_t kOptionInteractiveModePort = 0xFF03; static chip::Credentials::Examples::TestHarnessDACProvider mDacProvider; +static bool gInteractiveMode = false; +static chip::Optional gInteractiveModePort; bool AppOptions::HandleOptions(const char * program, OptionSet * options, int identifier, const char * name, const char * value) { @@ -34,6 +38,12 @@ bool AppOptions::HandleOptions(const char * program, OptionSet * options, int id case kOptionDacProviderFilePath: mDacProvider.Init(value); break; + case kOptionInteractiveMode: + gInteractiveMode = true; + break; + case kOptionInteractiveModePort: + gInteractiveModePort = chip::MakeOptional(static_cast(atoi(value))); + break; default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", program, name); retval = false; @@ -47,6 +57,8 @@ OptionSet * AppOptions::GetOptions() { static OptionDef optionsDef[] = { { "dac_provider", chip::ArgParser::kArgumentRequired, kOptionDacProviderFilePath }, + { "interactive", chip::ArgParser::kNoArgument, kOptionInteractiveMode }, + { "port", chip::ArgParser::kArgumentRequired, kOptionInteractiveModePort }, {}, }; @@ -54,6 +66,10 @@ OptionSet * AppOptions::GetOptions() AppOptions::HandleOptions, optionsDef, "PROGRAM OPTIONS", " --dac_provider \n" " A json file with data used by the example dac provider to validate device attestation procedure.\n" + " --interactive\n" + " Enable server interactive mode.\n" + " --port \n" + " Specify the listening port for the server interactive mode.\n" }; return &options; @@ -63,3 +79,13 @@ chip::Credentials::DeviceAttestationCredentialsProvider * AppOptions::GetDACProv { return &mDacProvider; } + +bool AppOptions::GetInteractiveMode() +{ + return gInteractiveMode; +} + +chip::Optional AppOptions::GetInteractiveModePort() +{ + return gInteractiveModePort; +} diff --git a/examples/placeholder/linux/AppOptions.h b/examples/placeholder/linux/AppOptions.h index 3073c66176331f..1451f325184111 100644 --- a/examples/placeholder/linux/AppOptions.h +++ b/examples/placeholder/linux/AppOptions.h @@ -27,6 +27,8 @@ class AppOptions public: static chip::ArgParser::OptionSet * GetOptions(); static chip::Credentials::DeviceAttestationCredentialsProvider * GetDACProvider(); + static bool GetInteractiveMode(); + static chip::Optional GetInteractiveModePort(); private: static bool HandleOptions(const char * program, chip::ArgParser::OptionSet * options, int identifier, const char * name, diff --git a/examples/placeholder/linux/BUILD.gn b/examples/placeholder/linux/BUILD.gn index 4599a450efae87..6b956056c3bc98 100644 --- a/examples/placeholder/linux/BUILD.gn +++ b/examples/placeholder/linux/BUILD.gn @@ -31,6 +31,7 @@ chip_data_model("configuration") { config("includes") { include_dirs = [ ".", + "${chip_root}/examples/common", "include", ] } @@ -38,6 +39,7 @@ config("includes") { executable("chip-${chip_tests_zap_config}") { sources = [ "AppOptions.cpp", + "InteractiveServer.cpp", "main.cpp", "src/bridged-actions-stub.cpp", "static-supported-modes-manager.cpp", @@ -45,6 +47,7 @@ executable("chip-${chip_tests_zap_config}") { deps = [ ":configuration", + "${chip_root}/examples/common/websocket-server", "${chip_root}/examples/platform/linux:app-main", "${chip_root}/src/app/tests/suites/commands/delay", "${chip_root}/src/app/tests/suites/commands/discovery", @@ -54,9 +57,10 @@ executable("chip-${chip_tests_zap_config}") { "${chip_root}/src/lib", "${chip_root}/src/lib/support:testing", # For sleepMillis. TODO: this is # odd and should be fixed + "${chip_root}/third_party/jsoncpp", ] - include_dirs = [ "include" ] + public_configs = [ ":includes" ] cflags = [ "-Wconversion" ] diff --git a/examples/placeholder/linux/InteractiveServer.cpp b/examples/placeholder/linux/InteractiveServer.cpp new file mode 100644 index 00000000000000..e780be8ef4dda3 --- /dev/null +++ b/examples/placeholder/linux/InteractiveServer.cpp @@ -0,0 +1,145 @@ +/* + * + * Copyright (c) 2023 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 "InteractiveServer.h" + +#include +#include + +using namespace chip::DeviceLayer; + +namespace { +constexpr const char * kClusterIdKey = "clusterId"; +constexpr const char * kEndpointIdKey = "endpointId"; +constexpr const char * kAttributeIdKey = "attributeId"; +constexpr const char * kWaitTypeKey = "waitType"; +constexpr const char * kAttributeWriteKey = "writeAttribute"; +constexpr const char * kAttributeReadKey = "readAttribute"; +constexpr const char * kCommandIdKey = "commandId"; +constexpr const char * kWaitForCommissioningCommand = "WaitForCommissioning"; + +std::string JsonToString(Json::Value & json) +{ + Json::FastWriter writer; + writer.omitEndingLineFeed(); + return writer.write(json); +} + +void OnPlatformEvent(const ChipDeviceEvent * event, intptr_t arg); + +void OnCommissioningComplete(intptr_t context) +{ + PlatformMgr().RemoveEventHandler(OnPlatformEvent); + InteractiveServer::GetInstance().CommissioningComplete(); +} + +void OnPlatformEvent(const ChipDeviceEvent * event, intptr_t arg) +{ + switch (event->Type) + { + case DeviceEventType::kCommissioningComplete: + PlatformMgr().ScheduleWork(OnCommissioningComplete, arg); + break; + } +} +} // namespace + +InteractiveServer * InteractiveServer::instance = nullptr; +InteractiveServer & InteractiveServer::GetInstance() +{ + if (instance == nullptr) + { + instance = new InteractiveServer(); + } + return *instance; +} + +void InteractiveServer::Run(const chip::Optional port) +{ + mIsReady = false; + wsThread = std::thread(&WebSocketServer::Run, &mWebSocketServer, port, this); +} + +bool InteractiveServer::OnWebSocketMessageReceived(char * msg) +{ + ChipLogError(chipTool, "Receive message: %s", msg); + if (strcmp(msg, kWaitForCommissioningCommand) == 0) + { + mIsReady = false; + PlatformMgr().AddEventHandler(OnPlatformEvent); + } + else + { + mIsReady = true; + } + return true; +} + +bool InteractiveServer::Command(const chip::app::ConcreteCommandPath & path) +{ + VerifyOrReturnValue(mIsReady, false); + + Json::Value value; + value[kClusterIdKey] = path.mClusterId; + value[kEndpointIdKey] = path.mEndpointId; + value[kCommandIdKey] = path.mCommandId; + + auto valueStr = JsonToString(value); + LogErrorOnFailure(mWebSocketServer.Send(valueStr.c_str())); + return mIsReady; +} + +bool InteractiveServer::ReadAttribute(const chip::app::ConcreteAttributePath & path) +{ + VerifyOrReturnValue(mIsReady, false); + + Json::Value value; + value[kClusterIdKey] = path.mClusterId; + value[kEndpointIdKey] = path.mEndpointId; + value[kAttributeIdKey] = path.mAttributeId; + value[kWaitTypeKey] = kAttributeReadKey; + + auto valueStr = JsonToString(value); + LogErrorOnFailure(mWebSocketServer.Send(valueStr.c_str())); + return mIsReady; +} + +bool InteractiveServer::WriteAttribute(const chip::app::ConcreteAttributePath & path) +{ + VerifyOrReturnValue(mIsReady, false); + + Json::Value value; + value[kClusterIdKey] = path.mClusterId; + value[kEndpointIdKey] = path.mEndpointId; + value[kAttributeIdKey] = path.mAttributeId; + value[kWaitTypeKey] = kAttributeWriteKey; + + auto valueStr = JsonToString(value); + LogErrorOnFailure(mWebSocketServer.Send(valueStr.c_str())); + return mIsReady; +} + +void InteractiveServer::CommissioningComplete() +{ + VerifyOrReturn(!mIsReady); + mIsReady = true; + + Json::Value value = Json::objectValue; + auto valueStr = JsonToString(value); + LogErrorOnFailure(mWebSocketServer.Send(valueStr.c_str())); +} diff --git a/examples/placeholder/linux/include/InteractiveServer.h b/examples/placeholder/linux/include/InteractiveServer.h new file mode 100644 index 00000000000000..9b454fc329362f --- /dev/null +++ b/examples/placeholder/linux/include/InteractiveServer.h @@ -0,0 +1,47 @@ +/* + * + * Copyright (c) 2023 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 +#include +#include + +class InteractiveServer : public WebSocketServerDelegate +{ +public: + static InteractiveServer & GetInstance(); + void Run(const chip::Optional port); + + bool Command(const chip::app::ConcreteCommandPath & path); + bool ReadAttribute(const chip::app::ConcreteAttributePath & path); + bool WriteAttribute(const chip::app::ConcreteAttributePath & path); + void CommissioningComplete(); + + /////////// WebSocketServerDelegate Interface ///////// + bool OnWebSocketMessageReceived(char * msg) override; + +private: + InteractiveServer(){}; + static InteractiveServer * instance; + + WebSocketServer mWebSocketServer; + std::thread wsThread; + bool mIsReady; +}; diff --git a/examples/placeholder/linux/include/MatterCallbacks.h b/examples/placeholder/linux/include/MatterCallbacks.h index a21a3b8bdc9ceb..0ddddb8f7d2ae9 100644 --- a/examples/placeholder/linux/include/MatterCallbacks.h +++ b/examples/placeholder/linux/include/MatterCallbacks.h @@ -18,6 +18,7 @@ #pragma once +#include "InteractiveServer.h" #include "Options.h" #include @@ -55,6 +56,8 @@ TestCommand * GetTargetTest() void MatterPostCommandReceivedCallback(const chip::app::ConcreteCommandPath & commandPath, const chip::Access::SubjectDescriptor & subjectDescriptor) { + VerifyOrReturn(!InteractiveServer::GetInstance().Command(commandPath)); + auto test = GetTargetTest(); VerifyOrReturn(test != nullptr && test->isRunning); @@ -66,6 +69,8 @@ void MatterPostCommandReceivedCallback(const chip::app::ConcreteCommandPath & co void MatterPostAttributeReadCallback(const chip::app::ConcreteAttributePath & attributePath) { + VerifyOrReturn(!InteractiveServer::GetInstance().ReadAttribute(attributePath)); + auto test = GetTargetTest(); VerifyOrReturn(test != nullptr && test->isRunning); @@ -77,6 +82,8 @@ void MatterPostAttributeReadCallback(const chip::app::ConcreteAttributePath & at void MatterPostAttributeWriteCallback(const chip::app::ConcreteAttributePath & attributePath) { + VerifyOrReturn(!InteractiveServer::GetInstance().WriteAttribute(attributePath)); + auto test = GetTargetTest(); VerifyOrReturn(test != nullptr && test->isRunning); diff --git a/examples/placeholder/linux/main.cpp b/examples/placeholder/linux/main.cpp index 627d24464e2bab..eb43b7ece228f7 100644 --- a/examples/placeholder/linux/main.cpp +++ b/examples/placeholder/linux/main.cpp @@ -33,6 +33,13 @@ int main(int argc, char * argv[]) } LinuxDeviceOptions::GetInstance().dacProvider = AppOptions::GetDACProvider(); + + auto & server = InteractiveServer::GetInstance(); + if (AppOptions::GetInteractiveMode()) + { + server.Run(AppOptions::GetInteractiveModePort()); + } + ChipLinuxAppMainLoop(); return 0; }