diff --git a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/CommandResponseHolder.java b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/CommandResponseHolder.java index 61ee303b408587..5b47762729316e 100644 --- a/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/CommandResponseHolder.java +++ b/examples/tv-app/android/App/content-app/src/main/java/com/example/contentapp/CommandResponseHolder.java @@ -31,6 +31,14 @@ private CommandResponseHolder() { Clusters.AccountLogin.Id, Clusters.AccountLogin.Commands.GetSetupPIN.ID, "{\"0\":\"20202021\"}"); + setResponseValue( + Clusters.AccountLogin.Id, + Clusters.AccountLogin.Commands.Login.ID, + "0"); + setResponseValue( + Clusters.AccountLogin.Id, + Clusters.AccountLogin.Commands.Logout.ID, + "0"); }; public static CommandResponseHolder getInstance() { diff --git a/examples/tv-app/android/include/account-login/AccountLoginManager.cpp b/examples/tv-app/android/include/account-login/AccountLoginManager.cpp index 12e6ba44dfb220..71c1bcc88e14e2 100644 --- a/examples/tv-app/android/include/account-login/AccountLoginManager.cpp +++ b/examples/tv-app/android/include/account-login/AccountLoginManager.cpp @@ -24,9 +24,34 @@ #include using namespace std; +using namespace chip::app::Clusters; using namespace chip::app::Clusters::AccountLogin; using Status = chip::Protocols::InteractionModel::Status; +namespace { + +const auto loginTempAccountIdentifierFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Login::Fields::kTempAccountIdentifier)); +const auto loginSetupPINFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Login::Fields::kSetupPIN)); +const auto loginNodeFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Login::Fields::kNode)); +const auto logoutNodeFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Logout::Fields::kNode)); + +string charSpanToString(const CharSpan& charSpan) { + return { charSpan.data(), charSpan.size() }; +} + +std::string serializeLoginCommand(AccountLogin::Commands::Login::Type cmd) { + return + R"({")" + loginTempAccountIdentifierFieldId + R"(":")" + charSpanToString(cmd.tempAccountIdentifier) + R"(",)" + + R"(")" + loginSetupPINFieldId + R"(":")" + charSpanToString(cmd.setupPIN) + R"(",)" + + R"(")" + loginNodeFieldId + R"(":")" + to_string(cmd.node.Value()) + R"("})"; +} + +std::string serializeLogoutCommand(AccountLogin::Commands::Logout::Type cmd) { + return R"({")" + logoutNodeFieldId + R"(":")" + to_string(cmd.node.Value()) + R"("})"; +} + +} + AccountLoginManager::AccountLoginManager(ContentAppCommandDelegate * commandDelegate, const char * setupPin) : mCommandDelegate(commandDelegate) { @@ -37,25 +62,61 @@ bool AccountLoginManager::HandleLogin(const CharSpan & tempAccountIdentifier, co const chip::Optional & nodeId) { ChipLogProgress(DeviceLayer, "AccountLoginManager::HandleLogin called for endpoint %d", mEndpointId); - string tempAccountIdentifierString(tempAccountIdentifier.data(), tempAccountIdentifier.size()); - string setupPinString(setupPin.data(), setupPin.size()); - if (strcmp(mSetupPin, setupPinString.c_str()) == 0) + if (mCommandDelegate == nullptr) { - ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin success"); - return true; + ChipLogError(Zcl, "CommandDelegate not found"); + return false; } - else - { - ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin failed expected pin %s", mSetupPin); + + if (tempAccountIdentifier.empty() || setupPIN.empty() || !nodeId.HasValue()) { + ChipLogError(Zcl, "Invalid parameters"); return false; } + + Json::Value response; + bool commandHandled = true; + AccountLogin::Commands::Login::Type cmd = { tempAccountIdentifier, setupPin, nodeId }; + + // Deliberately ignore returned status + mCommandDelegate->InvokeCommand(mEndpointId, AccountLogin::Id, + AccountLogin::Commands::Login::Id, + serializeLoginCommand(cmd), commandHandled, response); + + Status status = mCommandDelegate->FormatStatusResponse(response); + ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin command returned with status: %d", chip::to_underlying(status)); + + return status == chip::Protocols::InteractionModel::Status::Success; } bool AccountLoginManager::HandleLogout(const chip::Optional & nodeId) { - // TODO: Insert your code here to send logout request - return true; + ChipLogProgress(DeviceLayer, "AccountLoginManager::HandleLogout called for endpoint %d", mEndpointId); + + if (mCommandDelegate == nullptr) + { + ChipLogError(Zcl, "CommandDelegate not found"); + return false; + } + + if (!nodeId.HasValue()) { + ChipLogError(Zcl, "Invalid parameters"); + return false; + } + + Json::Value response; + bool commandHandled = true; + AccountLogin::Commands::Logout::Type cmd = { nodeId }; + + // Deliberately ignore returned status + mCommandDelegate->InvokeCommand(mEndpointId, AccountLogin::Id, + AccountLogin::Commands::Logout::Id, + serializeLogoutCommand(cmd), commandHandled, response); + + Status status = mCommandDelegate->FormatStatusResponse(response); + ChipLogProgress(Zcl, "AccountLoginManager::HandleLogout command returned with status: %d", chip::to_underlying(status)); + + return status == chip::Protocols::InteractionModel::Status::Success; } void AccountLoginManager::HandleGetSetupPin(CommandResponseHelper & helper, diff --git a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp index 199ec7761be0c8..0f65f4e686aeaa 100644 --- a/examples/tv-app/android/java/ContentAppCommandDelegate.cpp +++ b/examples/tv-app/android/java/ContentAppCommandDelegate.cpp @@ -39,13 +39,6 @@ namespace chip { namespace AppPlatform { -using CommandHandlerInterface = chip::app::CommandHandlerInterface; -using LaunchResponseType = chip::app::Clusters::ContentLauncher::Commands::LauncherResponse::Type; -using PlaybackResponseType = chip::app::Clusters::MediaPlayback::Commands::PlaybackResponse::Type; -using NavigateTargetResponseType = chip::app::Clusters::TargetNavigator::Commands::NavigateTargetResponse::Type; -using GetSetupPINResponseType = chip::app::Clusters::AccountLogin::Commands::GetSetupPINResponse::Type; -using Status = chip::Protocols::InteractionModel::Status; - const std::string FAILURE_KEY = "PlatformError"; const std::string FAILURE_STATUS_KEY = "Status"; @@ -107,6 +100,7 @@ void ContentAppCommandDelegate::InvokeCommand(CommandHandlerInterface::HandlerCo Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clusterId, CommandId commandId, std::string payload, bool & commandHandled, Json::Value & value) { + // Q1: It seems like this InvokeCommand *never* returns success status. if (epId >= FIXED_ENDPOINT_COUNT) { JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); @@ -153,6 +147,7 @@ Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clust return chip::Protocols::InteractionModel::Status::Failure; } + // Q: It would seem that this is the successfull return case (?) return chip::Protocols::InteractionModel::Status::UnsupportedEndpoint; } else @@ -231,20 +226,30 @@ void ContentAppCommandDelegate::FormatResponseData(CommandHandlerInterface::Hand } case app::Clusters::AccountLogin::Id: { - if (app::Clusters::AccountLogin::Commands::GetSetupPIN::Id != handlerContext.mRequestPath.mCommandId) - { - // No response for other commands in this cluster - break; - } - Status status; - GetSetupPINResponseType getSetupPINresponse = FormatGetSetupPINResponse(value, status); - if (status != chip::Protocols::InteractionModel::Status::Success) - { - handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, status); - } - else - { - handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, getSetupPINresponse); + switch (handlerContext.mRequestPath.mCommandId) { + case app::Clusters::AccountLogin::Commands::GetSetupPIN::Id: { + Status status; + GetSetupPINResponseType getSetupPINresponse = FormatGetSetupPINResponse(value, status); + if (status != chip::Protocols::InteractionModel::Status::Success) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, status); + } + else + { + handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, getSetupPINresponse); + } + break; + } + case app::Clusters::AccountLogin::Commands::Login::Id: { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, FormatStatusResponse(value)); + break; + } + case app::Clusters::AccountLogin::Commands::Logout::Id: { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, FormatStatusResponse(value)); + break; + } + default: + break; } break; } @@ -342,5 +347,16 @@ GetSetupPINResponseType ContentAppCommandDelegate::FormatGetSetupPINResponse(Jso return getSetupPINresponse; } +Status ContentAppCommandDelegate::FormatStatusResponse(Json::Value value) +{ + if (value.isUInt()) { + return static_cast(value.asUInt()); + } + else + { + return chip::Protocols::InteractionModel::Status::Failure; + } +} + } // namespace AppPlatform } // namespace chip diff --git a/examples/tv-app/android/java/ContentAppCommandDelegate.h b/examples/tv-app/android/java/ContentAppCommandDelegate.h index f033b1afa79189..49d3e6fda87610 100644 --- a/examples/tv-app/android/java/ContentAppCommandDelegate.h +++ b/examples/tv-app/android/java/ContentAppCommandDelegate.h @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -75,6 +76,7 @@ class ContentAppCommandDelegate : public CommandHandlerInterface LaunchResponseType FormatContentLauncherResponse(Json::Value value, Status & status); NavigateTargetResponseType FormatNavigateTargetResponse(Json::Value value, Status & status); PlaybackResponseType FormatMediaPlaybackResponse(Json::Value value, Status & status); + Status FormatStatusResponse(Json::Value value); private: void InitializeJNIObjects(jobject manager) diff --git a/src/controller/CommissionerDiscoveryController.cpp b/src/controller/CommissionerDiscoveryController.cpp index 7f13a4323d2f5c..1d9421da09af38 100644 --- a/src/controller/CommissionerDiscoveryController.cpp +++ b/src/controller/CommissionerDiscoveryController.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY @@ -405,6 +406,7 @@ void CommissionerDiscoveryController::InternalHandleContentAppPasscodeResponse() ValidateSession(); uint32_t passcode = mPasscode; + // Q: Seems like getting rotating ID is done twice - once here and once in InternalOk. Is this necessary or could it be cached? if (mUdcServer == nullptr) { ChipLogError(AppServer, "UX Ok - HandleContentAppPasscodeResponse: no udc server"); @@ -440,6 +442,9 @@ void CommissionerDiscoveryController::InternalHandleContentAppPasscodeResponse() } CharSpan rotatingIdSpan(rotatingIdBuffer, 2 * rotatingIdLength); + // Store rotating ID as tempAccountIdentifier to use in AccountLogin::Login payload later. + mTempAccountIdentifier = rotatingIdSpan; + // first step of commissioner passcode ChipLogError(AppServer, "UX Ok: commissioner passcode, sending CDC"); // generate a passcode @@ -455,6 +460,10 @@ void CommissionerDiscoveryController::InternalHandleContentAppPasscodeResponse() cd, Transport::PeerAddress::UDP(client->GetPeerAddress().GetIPAddress(), client->GetCdPort())); return; } + // Store commissioner passcode as setup PIN (string/charspan). + // If this PIN is not empty it signals that user prompt was shown to enter PIN and AccountLogin::Login command has to be sent. + mCommissionerSetupPin = CharSpan::fromCharString(std::to_string(passcode)); + client->SetCachedCommissionerPasscode(passcode); client->SetUDCClientProcessingState(UDCClientProcessingState::kWaitingForCommissionerPasscodeReady); @@ -606,6 +615,23 @@ void CommissionerDiscoveryController::CommissioningSucceeded(uint16_t vendorId, mVendorId = vendorId; mProductId = productId; mNodeId = nodeId; + + // Send AccountLogin::Login command if user was prompted to enter setup PIN manually. + // Q: Is this the correct place to have this logic? + // Q: Is there an easier way call AccountLoginDelegate from here? + if (!mCommissionerSetupPIN.empty()) { + ChipLogProgress(AppServer, "UX ComissioningSucceeded with setupPIN prompt flow"); + auto app = ContentAppPlatform::GetInstance().LoadContentAppByClient(vendorId, productId); + if (app == nullptr) { + ChipLogError(AppServer, "UX ComissioningSucceeded with setupPIN prompt flow: Failed to get ContentApp"); + // Q: Any action to take? + } else { + auto status = app->GetAccountLoginDelegate()->HandleLogin(mTempAccountIdentifier, mCommissionerSetupPin, {mNodeId}); + ChipLogProgress(AppServer, "UX ComissioningSucceeded with setupPIN prompt flow: HandleLogin response status: %d", status); + // Q: Any action to take here if status is true/false? + } + } + if (mPostCommissioningListener != nullptr) { ChipLogDetail(Controller, "CommissionerDiscoveryController calling listener"); diff --git a/src/controller/CommissionerDiscoveryController.h b/src/controller/CommissionerDiscoveryController.h index 5f7572b29def17..a23cd153d567cf 100644 --- a/src/controller/CommissionerDiscoveryController.h +++ b/src/controller/CommissionerDiscoveryController.h @@ -38,6 +38,7 @@ #if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY +using chip::CharSpan; using chip::NodeId; using chip::OperationalSessionSetup; using chip::Protocols::UserDirectedCommissioning::UDCClientState; @@ -451,6 +452,8 @@ class CommissionerDiscoveryController : public chip::Protocols::UserDirectedComm uint16_t mProductId = 0; NodeId mNodeId = 0; uint32_t mPasscode = 0; + CharSpan mTempAccountIdentifier; + CharSpan mCommissionerSetupPin; UserDirectedCommissioningServer * mUdcServer = nullptr; UserPrompter * mUserPrompter = nullptr;