From 666427fc34452cd34d8393b2dd75bb6aae0444cd Mon Sep 17 00:00:00 2001 From: Sharad Binjola Date: Mon, 9 Oct 2023 15:26:45 -0700 Subject: [PATCH] Linux tv-casting-app: simplified CastingPlayer.VerifyOrEstablishConnection API --- examples/tv-casting-app/APIs.md | 34 +- .../src/main/jni/cpp/core/CastingApp-JNI.cpp | 18 +- examples/tv-casting-app/linux/BUILD.gn | 2 + .../linux/simple-app-helper.cpp | 177 +++++++++ .../tv-casting-app/linux/simple-app-helper.h | 92 +++++ examples/tv-casting-app/linux/simple-app.cpp | 47 +-- .../tv-casting-app/tv-casting-common/BUILD.gn | 5 + .../tv-casting-common/core/CastingApp.cpp | 6 +- .../tv-casting-common/core/CastingApp.h | 5 + .../tv-casting-common/core/CastingPlayer.cpp | 275 ++++++++++++++ .../tv-casting-common/core/CastingPlayer.h | 201 ++++++---- .../core/CastingPlayerDiscovery.cpp | 21 +- .../support/CastingStore.cpp | 343 ++++++++++++++++++ .../tv-casting-common/support/CastingStore.h | 97 +++++ .../support/ChipDeviceEventHandler.cpp | 189 ++++++++++ .../support/ChipDeviceEventHandler.h | 84 +++++ 16 files changed, 1465 insertions(+), 131 deletions(-) create mode 100644 examples/tv-casting-app/linux/simple-app-helper.cpp create mode 100644 examples/tv-casting-app/linux/simple-app-helper.h create mode 100644 examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp create mode 100644 examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp create mode 100644 examples/tv-casting-app/tv-casting-common/support/CastingStore.h create mode 100644 examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp create mode 100644 examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index 8a6212540be2f2..a4541d5d8a506f 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -405,7 +405,7 @@ func initialize() -> MatterError { ### Discover Casting Players -_{Complete Discovery examples: [Linux](linux/simple-app.cpp)}_ +_{Complete Discovery examples: [Linux](linux/simple-app-helper.cpp)}_ The Casting Client discovers `CastingPlayers` using Matter Commissioner discovery over DNS-SD by listening for `CastingPlayer` events as they are @@ -444,12 +444,14 @@ singleton instance. Then, call `StartDiscovery` by optionally specifying the `kTargetPlayerDeviceType` to filter results by. ```c +const uint64_t kTargetPlayerDeviceType = 35; // 35 represents device type of Matter Video Player +... +... DiscoveryDelegateImpl delegate; CastingPlayerDiscovery::GetInstance()->SetDelegate(&delegate); VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, ChipLogError(AppServer, "CastingPlayerDiscovery::SetDelegate failed %" CHIP_ERROR_FORMAT, err.Format())); -const uint64_t kTargetPlayerDeviceType = 35; // 35 represents device type of Matter Video Player err = CastingPlayerDiscovery::GetInstance()->StartDiscovery(kTargetPlayerDeviceType); VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, ChipLogError(AppServer, "CastingPlayerDiscovery::StartDiscovery failed %" CHIP_ERROR_FORMAT, err.Format())); @@ -459,6 +461,34 @@ chip::DeviceLayer::PlatformMgr().RunEventLoop(); ### Connect to a Casting Player +_{Complete Discovery examples: [Linux](linux/simple-app-helper.cpp)}_ + +Each CastingPlayer object created during [Discovery](#discovery) contains +information such as deviceName, vendorId, productId, etc. which can help the +user pick the right CastingPlayer. A Casting Client can attempt to connect to +the selectedCastingPlayer using +[Matter User Directed Commissioning (UDC)](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/rendezvous/UserDirectedCommissioning.adoc). +The Matter TV Casting library locally caches information required to reconnect +to a CastingPlayer, once the Casting client has been commissioned by it. After +that, the Casting client is able to skip the full UDC process by establishing +CASE with the CastingPlayer directly. Once connected, the CastingPlayer object +will contain the list of available Endpoints on that CastingPlayer. + +On Linux, the Casting Client can connect to a `CastingPlayer` by successfully +calling `VerifyOrEstablishConnection` on it. + +```c +void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) +{ + ChipLogProgress(AppServer, "ConnectionHandler called with %" CHIP_ERROR_FORMAT, err.Format()); +} + +... +// targetCastingPlayer is a discovered CastingPlayer +targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler); +... +``` + ### Select an Endpoint on the Casting Player ## Interacting with a Casting Endpoint diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp index 984287af8c30fd..90a36bbfec92ca 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/core/CastingApp-JNI.cpp @@ -21,7 +21,10 @@ #include "../JNIDACProvider.h" #include "../support/ErrorConverter-JNI.h" #include "../support/RotatingDeviceIdUniqueIdProvider-JNI.h" -#include "core/CastingApp.h" // from tv-casting-common + +// from tv-casting-common +#include "core/CastingApp.h" +#include "support/ChipDeviceEventHandler.h" #include #include @@ -79,20 +82,25 @@ JNI_METHOD(jobject, finishStartup)(JNIEnv *, jobject) { chip::DeviceLayer::StackLock lock; ChipLogProgress(AppServer, "JNI_METHOD CastingAppJNI.finishStartup called"); - auto & server = chip::Server::GetInstance(); + + CHIP_ERROR err = CHIP_NO_ERROR; + auto & server = chip::Server::GetInstance(); // TODO: Set AppDelegate // &server.GetCommissioningWindowManager().SetAppDelegate(??); // Initialize binding handlers - chip::BindingManager::GetInstance().Init( + err = chip::BindingManager::GetInstance().Init( { &server.GetFabricTable(), server.GetCASESessionManager(), &server.GetPersistentStorage() }); + VerifyOrReturnValue(err == CHIP_NO_ERROR, support::createJMatterError(err), + ChipLogError(AppServer, "Failed to init BindingManager %" CHIP_ERROR_FORMAT, err.Format())); // TODO: Set FabricDelegate // chip::Server::GetInstance().GetFabricTable().AddFabricDelegate(&mPersistenceManager); - // TODO: Add DeviceEvent Handler - // ReturnErrorOnFailure(DeviceLayer::PlatformMgrImpl().AddEventHandler(DeviceEventCallback, 0)); + err = chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(support::ChipDeviceEventHandler::Handle, 0); + VerifyOrReturnValue(err == CHIP_NO_ERROR, support::createJMatterError(err), + ChipLogError(AppServer, "Failed to register ChipDeviceEventHandler %" CHIP_ERROR_FORMAT, err.Format())); return support::createJMatterError(CHIP_NO_ERROR); } diff --git a/examples/tv-casting-app/linux/BUILD.gn b/examples/tv-casting-app/linux/BUILD.gn index 113c6f56219f7d..0e4b9820538412 100644 --- a/examples/tv-casting-app/linux/BUILD.gn +++ b/examples/tv-casting-app/linux/BUILD.gn @@ -27,6 +27,8 @@ executable("chip-tv-casting-app") { if (chip_casting_simplified) { sources = [ "${chip_root}/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h", + "simple-app-helper.cpp", + "simple-app-helper.h", "simple-app.cpp", ] } else { diff --git a/examples/tv-casting-app/linux/simple-app-helper.cpp b/examples/tv-casting-app/linux/simple-app-helper.cpp new file mode 100644 index 00000000000000..dab134dfb7b57c --- /dev/null +++ b/examples/tv-casting-app/linux/simple-app-helper.cpp @@ -0,0 +1,177 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * + * 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 "simple-app-helper.h" + +#include "app/clusters/bindings/BindingManager.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DiscoveryDelegateImpl * DiscoveryDelegateImpl::_discoveryDelegateImpl = nullptr; + +DiscoveryDelegateImpl * DiscoveryDelegateImpl::GetInstance() +{ + if (_discoveryDelegateImpl == nullptr) + { + _discoveryDelegateImpl = new DiscoveryDelegateImpl(); + } + return _discoveryDelegateImpl; +} + +void DiscoveryDelegateImpl::HandleOnAdded(matter::casting::memory::Strong player) +{ + if (commissionersCount == 0) + { + ChipLogProgress(AppServer, "Select discovered Casting Player (start index = 0) to request commissioning"); + + ChipLogProgress(AppServer, "Example: cast request 0"); + } + ChipLogProgress(AppServer, "Discovered CastingPlayer #%d", commissionersCount); + ++commissionersCount; + player->LogDetail(); +} + +void DiscoveryDelegateImpl::HandleOnUpdated(matter::casting::memory::Strong player) +{ + ChipLogProgress(AppServer, "Updated CastingPlayer with ID: %s", player->GetId()); +} + +void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) +{ + ChipLogProgress(AppServer, "ConnectionHandler called with %" CHIP_ERROR_FORMAT, err.Format()); +} + +#if defined(ENABLE_CHIP_SHELL) +void RegisterCommands() +{ + static const chip::Shell::shell_command_t sDeviceComand = { &CommandHandler, "cast", + "Casting commands. Usage: cast [command_name]" }; + + // Register the root `device` command with the top-level shell. + chip::Shell::Engine::Root().RegisterCommands(&sDeviceComand, 1); +} + +CHIP_ERROR CommandHandler(int argc, char ** argv) +{ + if (argc == 0 || strcmp(argv[0], "help") == 0) + { + return PrintAllCommands(); + } + if (strcmp(argv[0], "discover") == 0) + { + ChipLogProgress(AppServer, "discover"); + + return matter::casting::core::CastingPlayerDiscovery::GetInstance()->StartDiscovery(kTargetPlayerDeviceType); + } + if (strcmp(argv[0], "stop-discovery") == 0) + { + ChipLogProgress(AppServer, "stop-discovery"); + return matter::casting::core::CastingPlayerDiscovery::GetInstance()->StopDiscovery(); + } + if (strcmp(argv[0], "request") == 0) + { + ChipLogProgress(AppServer, "request"); + if (argc < 2) + { + return PrintAllCommands(); + } + char * eptr; + unsigned long index = static_cast(strtol(argv[1], &eptr, 10)); + std::vector> castingPlayers = + matter::casting::core::CastingPlayerDiscovery::GetInstance()->GetCastingPlayers(); + VerifyOrReturnValue(0 <= index && index < castingPlayers.size(), CHIP_ERROR_INVALID_ARGUMENT, + ChipLogError(AppServer, "Invalid casting player index provided: %lu", index)); + std::shared_ptr targetCastingPlayer = castingPlayers.at(index); + targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler); + return CHIP_NO_ERROR; + } + if (strcmp(argv[0], "print-bindings") == 0) + { + PrintBindings(); + return CHIP_NO_ERROR; + } + if (strcmp(argv[0], "print-fabrics") == 0) + { + PrintFabrics(); + return CHIP_NO_ERROR; + } + if (strcmp(argv[0], "delete-fabric") == 0) + { + char * eptr; + chip::FabricIndex fabricIndex = (chip::FabricIndex) strtol(argv[1], &eptr, 10); + chip::Server::GetInstance().GetFabricTable().Delete(fabricIndex); + return CHIP_NO_ERROR; + } + return CHIP_ERROR_INVALID_ARGUMENT; +} + +CHIP_ERROR PrintAllCommands() +{ + chip::Shell::streamer_t * sout = chip::Shell::streamer_get(); + streamer_printf(sout, " help Usage: cast \r\n"); + streamer_printf(sout, " print-bindings Usage: cast print-bindings\r\n"); + streamer_printf(sout, " print-fabrics Usage: cast print-fabrics\r\n"); + streamer_printf( + sout, + " delete-fabric Delete a fabric from the casting client's fabric store. Usage: cast delete-fabric 1\r\n"); + streamer_printf(sout, " discover Discover Casting Players. Usage: cast discover\r\n"); + streamer_printf(sout, " stop-discovery Stop Discovery of Casting Players. Usage: cast stop-discovery\r\n"); + streamer_printf( + sout, " request Request connecting to discovered Casting Player with [index]. Usage: cast request 0\r\n"); + streamer_printf(sout, "\r\n"); + + return CHIP_NO_ERROR; +} + +void PrintBindings() +{ + for (const auto & binding : chip::BindingTable::GetInstance()) + { + ChipLogProgress(AppServer, + "Binding type=%d fab=%d nodeId=0x" ChipLogFormatX64 + " groupId=%d local endpoint=%d remote endpoint=%d cluster=" ChipLogFormatMEI, + binding.type, binding.fabricIndex, ChipLogValueX64(binding.nodeId), binding.groupId, binding.local, + binding.remote, ChipLogValueMEI(binding.clusterId.ValueOr(0))); + } +} + +void PrintFabrics() +{ + // set fabric to be the first in the list + for (const auto & fb : chip::Server::GetInstance().GetFabricTable()) + { + chip::FabricIndex fabricIndex = fb.GetFabricIndex(); + ChipLogError(AppServer, "Next Fabric index=%d", fabricIndex); + if (!fb.IsInitialized()) + { + ChipLogError(AppServer, " -- Not initialized"); + continue; + } + chip::NodeId myNodeId = fb.GetNodeId(); + ChipLogProgress(AppServer, + "---- Current Fabric nodeId=0x" ChipLogFormatX64 " fabricId=0x" ChipLogFormatX64 " fabricIndex=%d", + ChipLogValueX64(myNodeId), ChipLogValueX64(fb.GetFabricId()), fabricIndex); + } +} + +#endif // ENABLE_CHIP_SHELL diff --git a/examples/tv-casting-app/linux/simple-app-helper.h b/examples/tv-casting-app/linux/simple-app-helper.h new file mode 100644 index 00000000000000..d48b082e0faf30 --- /dev/null +++ b/examples/tv-casting-app/linux/simple-app-helper.h @@ -0,0 +1,92 @@ +/* + * + * 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 "core/CastingPlayer.h" +#include "core/CastingPlayerDiscovery.h" +#include "core/Types.h" + +#include + +/** + * @brief Matter deviceType that the Linux tv-casting-app will discover over Commissionable Node discovery (DNS-SD) + * + * 35 represents device type of Matter Video Player + */ +const uint64_t kTargetPlayerDeviceType = 35; + +/** + * @brief Singleton that reacts to CastingPlayer discovery results + */ +class DiscoveryDelegateImpl : public matter::casting::core::DiscoveryDelegate +{ +private: + DiscoveryDelegateImpl(){}; + static DiscoveryDelegateImpl * _discoveryDelegateImpl; + int commissionersCount = 0; + +public: + static DiscoveryDelegateImpl * GetInstance(); + + /** + * @brief Called when a new CastingPlayer is discovered + * + * @param player the discovered CastingPlayer + */ + void HandleOnAdded(matter::casting::memory::Strong player) override; + + /** + * @brief Called when there are updates to the Attributes of a previously discovered CastingPlayer + * + * @param player the updated CastingPlayer + */ + void HandleOnUpdated(matter::casting::memory::Strong player) override; +}; + +/** + * @brief Linux tv-casting-app's onCompleted handler for CastingPlayer.VerifyOrEstablishConnection API + */ +void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer); + +#if defined(ENABLE_CHIP_SHELL) +/** + * @brief Register CHIP Shell commands for the Linux tv-casting-app CLI + */ +void RegisterCommands(); + +/** + * @brief Top level handler that parses text entered on the Linux tv-casting-app CLI and invokes the appropriate handler + */ +CHIP_ERROR CommandHandler(int argc, char ** argv); + +/** + * @brief Prints all the commands available on the Linux tv-casting-app CLI + */ +CHIP_ERROR PrintAllCommands(); + +/** + * @brief Implements the "print-bindings" command + */ +void PrintBindings(); + +/** + * @brief Implements the "print-fabrics" command + */ +void PrintFabrics(); +#endif diff --git a/examples/tv-casting-app/linux/simple-app.cpp b/examples/tv-casting-app/linux/simple-app.cpp index cd2f71eb612159..d0a23769ea600b 100644 --- a/examples/tv-casting-app/linux/simple-app.cpp +++ b/examples/tv-casting-app/linux/simple-app.cpp @@ -16,6 +16,8 @@ * limitations under the License. */ +#include "simple-app-helper.h" + #include "core/CastingPlayer.h" #include "core/CastingPlayerDiscovery.h" #include "core/Types.h" @@ -31,6 +33,12 @@ #include #include +#if defined(ENABLE_CHIP_SHELL) +#include // nogncheck +#include +using chip::Shell::Engine; +#endif + using namespace matter::casting::core; using namespace matter::casting::support; @@ -78,33 +86,6 @@ CHIP_ERROR InitCommissionableDataProvider(LinuxCommissionableDataProvider & prov options.payload.discriminator.GetLongValue()); } -/** - * @brief React to discovery results - */ -class DiscoveryDelegateImpl : public DiscoveryDelegate -{ -private: - int commissionersCount = 0; - -public: - void HandleOnAdded(matter::casting::memory::Strong player) override - { - if (commissionersCount == 0) - { - ChipLogProgress(AppServer, "Select discovered CastingPlayer to request commissioning"); - - ChipLogProgress(AppServer, "Example: cast request 0"); - } - ++commissionersCount; - ChipLogProgress(AppServer, "Discovered CastingPlayer #%d", commissionersCount); - player->LogDetail(); - } - void HandleOnUpdated(matter::casting::memory::Strong player) override - { - ChipLogProgress(AppServer, "Updated CastingPlayer with ID: %s", player->GetId()); - } -}; - /** * @brief Provides the unique ID that is used by the SDK to generate the Rotating Device ID. */ @@ -177,14 +158,18 @@ int main(int argc, char * argv[]) VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, ChipLogError(AppServer, "CastingApp::Start failed %" CHIP_ERROR_FORMAT, err.Format())); - DiscoveryDelegateImpl delegate; - CastingPlayerDiscovery::GetInstance()->SetDelegate(&delegate); +#if defined(ENABLE_CHIP_SHELL) + chip::Shell::Engine::Root().Init(); + std::thread shellThread([]() { chip::Shell::Engine::Root().RunMainLoop(); }); + RegisterCommands(); +#endif + + CastingPlayerDiscovery::GetInstance()->SetDelegate(DiscoveryDelegateImpl::GetInstance()); VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, ChipLogError(AppServer, "CastingPlayerDiscovery::SetDelegate failed %" CHIP_ERROR_FORMAT, err.Format())); // Discover CastingPlayers - const uint64_t kTargetPlayerDeviceType = 35; // 35 represents device type of Matter Video Player - err = CastingPlayerDiscovery::GetInstance()->StartDiscovery(kTargetPlayerDeviceType); + err = CastingPlayerDiscovery::GetInstance()->StartDiscovery(kTargetPlayerDeviceType); VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, ChipLogError(AppServer, "CastingPlayerDiscovery::StartDiscovery failed %" CHIP_ERROR_FORMAT, err.Format())); diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index ece67da9e6cc82..4e87c59f118a99 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -95,11 +95,16 @@ chip_data_model("tv-casting-common") { sources += [ "core/CastingApp.cpp", "core/CastingApp.h", + "core/CastingPlayer.cpp", "core/CastingPlayer.h", "core/CastingPlayerDiscovery.cpp", "core/CastingPlayerDiscovery.h", "core/Types.h", "support/AppParameters.h", + "support/CastingStore.cpp", + "support/CastingStore.h", + "support/ChipDeviceEventHandler.cpp", + "support/ChipDeviceEventHandler.h", "support/DataProvider.h", ] diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp index bc495a27fff870..45f0efdc913e47 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp +++ b/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp @@ -18,6 +18,8 @@ #include "CastingApp.h" +#include "support/ChipDeviceEventHandler.h" + #include #include #include @@ -109,8 +111,8 @@ CHIP_ERROR CastingApp::PostStartRegistrations() // TODO: Set FabricDelegate // chip::Server::GetInstance().GetFabricTable().AddFabricDelegate(&mPersistenceManager); - // TODO: Add DeviceEvent Handler - // ReturnErrorOnFailure(DeviceLayer::PlatformMgrImpl().AddEventHandler(DeviceEventCallback, 0)); + // Register DeviceEvent Handler + ReturnErrorOnFailure(chip::DeviceLayer::PlatformMgrImpl().AddEventHandler(ChipDeviceEventHandler::Handle, 0)); mState = CASTING_APP_RUNNING; // CastingApp started successfully, set state to RUNNING return CHIP_NO_ERROR; diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingApp.h b/examples/tv-casting-app/tv-casting-common/core/CastingApp.h index 25312daaa4cecf..84bff301dfb1cf 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingApp.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingApp.h @@ -67,6 +67,11 @@ class CastingApp */ CHIP_ERROR Stop(); + /** + * @return true, if CastingApp is in CASTING_APP_RUNNING state. false otherwise + */ + bool isRunning() { return mState == CASTING_APP_RUNNING; } + private: CastingApp(); static CastingApp * _castingApp; diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp new file mode 100644 index 00000000000000..9cc11a774e1564 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp @@ -0,0 +1,275 @@ +/* + * + * 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 "CastingPlayer.h" +#include "support/CastingStore.h" + +#include + +namespace matter { +namespace casting { +namespace core { + +CastingPlayer * CastingPlayer::mTargetCastingPlayer = nullptr; + +void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, unsigned long long int commissioningWindowTimeoutSec) +{ + ChipLogProgress(AppServer, "CastingPlayer::VerifyOrEstablishConnection called"); + + std::vector::iterator it; + std::vector cachedCastingPlayers = support::CastingStore::GetInstance()->ReadAll(); + + CHIP_ERROR err = CHIP_NO_ERROR; + + // ensure the app was not already in the process of connecting to this CastingPlayer + err = (mConnectionState != CASTING_PLAYER_CONNECTING ? CHIP_NO_ERROR : CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(mConnectionState != CASTING_PLAYER_CONNECTING, + ChipLogError(AppServer, + "CastingPlayer::VerifyOrEstablishConnection called while already connecting to this CastingPlayer")); + mConnectionState = CASTING_PLAYER_CONNECTING; + mOnCompleted = onCompleted; + mCommissioningWindowTimeoutSec = commissioningWindowTimeoutSec; + mTargetCastingPlayer = this; + + // If this CastingPlayer is the cache of CastingPlayers the app previously connected to (and has nodeId and fabricIndex of), + // simply Find or Re-establish the CASE session and return early + if (cachedCastingPlayers.size() != 0) + { + it = std::find_if(cachedCastingPlayers.begin(), cachedCastingPlayers.end(), + [this](const core::CastingPlayer & castingPlayerParam) { return castingPlayerParam == *this; }); + + if (it != cachedCastingPlayers.end()) + { + unsigned index = (unsigned int) std::distance(cachedCastingPlayers.begin(), it); + *this = cachedCastingPlayers[index]; + + FindOrEstablishSession( + nullptr, + [](void * context, chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle) { + ChipLogProgress(AppServer, "CastingPlayer::VerifyOrEstablishConnection Connection to CastingPlayer successful"); + CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_CONNECTED; + support::CastingStore::GetInstance()->AddOrUpdate(*CastingPlayer::GetTargetCastingPlayer()); + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(CHIP_NO_ERROR, CastingPlayer::GetTargetCastingPlayer()); + }, + [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { + ChipLogError(AppServer, "CastingPlayer::VerifyOrEstablishConnection Connection to CastingPlayer failed"); + CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_NOT_CONNECTED; + support::CastingStore::GetInstance()->Delete(*CastingPlayer::GetTargetCastingPlayer()); + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(error, nullptr); + mTargetCastingPlayer = nullptr; + }); + return; // FindOrEstablishSession called. Return early. + } + } + + // this CastingPlayer is not in the list of cached CastingPlayers previously connected to. This VerifyOrEstablishConnection call + // will require User Directed Commissioning. + if (chip::Server::GetInstance().GetFailSafeContext().IsFailSafeArmed()) + { + ChipLogProgress(AppServer, "CastingPlayer::VerifyOrEstablishConnection Forcing expiry of armed FailSafe timer"); + // ChipDeviceEventHandler will handle the kFailSafeTimerExpired event by Opening the Basic Commissioning Window and Sending + // the User Directed Commissioning Request + chip::Server::GetInstance().GetFailSafeContext().ForceFailSafeTimerExpiry(); + } + else + { + SuccessOrExit(err = chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow( + chip::System::Clock::Seconds16(mCommissioningWindowTimeoutSec))); + +#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT + SuccessOrExit(err = SendUserDirectedCommissioningRequest()); +#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT + } + +exit: + if (err != CHIP_NO_ERROR) + { + support::ChipDeviceEventHandler::SetUdcStatus(false); + mConnectionState = CASTING_PLAYER_NOT_CONNECTED; + mCommissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec; + mOnCompleted = nullptr; + mTargetCastingPlayer = nullptr; + ChipLogError(AppServer, "CastingPlayer::VerifyOrEstablishConnection failed with %" CHIP_ERROR_FORMAT, err.Format()); + mOnCompleted(err, nullptr); + } +} + +#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT +CHIP_ERROR CastingPlayer::SendUserDirectedCommissioningRequest() +{ + chip::Inet::IPAddress * ipAddressToUse = GetIpAddressForUDCRequest(); + VerifyOrReturnValue(ipAddressToUse != nullptr, CHIP_ERROR_INCORRECT_STATE, + ChipLogError(AppServer, "No IP Address found to send UDC request to")); + + ReturnErrorOnFailure(chip::Server::GetInstance().SendUserDirectedCommissioningRequest( + chip::Transport::PeerAddress::UDP(*ipAddressToUse, mAttributes.port, mAttributes.interfaceId))); + + ReturnErrorOnFailure(support::ChipDeviceEventHandler::SetUdcStatus(true)); + + return CHIP_NO_ERROR; +} + +chip::Inet::IPAddress * CastingPlayer::GetIpAddressForUDCRequest() +{ + size_t ipIndexToUse = 0; + for (size_t i = 0; i < mAttributes.numIPs; i++) + { + if (mAttributes.ipAddresses[i].IsIPv4()) + { + ipIndexToUse = i; + ChipLogProgress(AppServer, "Found IPv4 address at index: %lu - prioritizing use of IPv4", + static_cast(ipIndexToUse)); + break; + } + + if (i == (mAttributes.numIPs - 1)) + { + ChipLogProgress(AppServer, "Could not find an IPv4 address, defaulting to the first address in IP list"); + } + } + + return &mAttributes.ipAddresses[ipIndexToUse]; +} +#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT + +void CastingPlayer::FindOrEstablishSession(void * clientContext, chip::OnDeviceConnected onDeviceConnected, + chip::OnDeviceConnectionFailure onDeviceConnectionFailure) +{ + ChipLogProgress(AppServer, "CastingPlayer.FindOrEstablishSession called on nodeId=0x" ChipLogFormatX64 " fabricIndex=%d", + ChipLogValueX64(mAttributes.nodeId), mAttributes.fabricIndex); + VerifyOrReturn(mAttributes.nodeId != 0 && mAttributes.fabricIndex != 0, + ChipLogError(AppServer, "CastingPlayer.FindOrEstablishSession called on invalid nodeId/fabricIndex")); + + ConnectionContext * connectionContext = + new ConnectionContext(clientContext, this, onDeviceConnected, onDeviceConnectionFailure); + + chip::Server::GetInstance().GetCASESessionManager()->FindOrEstablishSession( + chip::ScopedNodeId(mAttributes.nodeId, mAttributes.fabricIndex), connectionContext->mOnConnectedCallback, + connectionContext->mOnConnectionFailureCallback); +} + +void CastingPlayer::LogDetail() const +{ + if (strlen(mAttributes.id) != 0) + { + ChipLogDetail(AppServer, "\tID: %s", mAttributes.id); + } + if (strlen(mAttributes.deviceName) != 0) + { + ChipLogDetail(AppServer, "\tName: %s", mAttributes.deviceName); + } + if (strlen(mAttributes.hostName) != 0) + { + ChipLogDetail(AppServer, "\tHost Name: %s", mAttributes.hostName); + } + if (strlen(mAttributes.instanceName) != 0) + { + ChipLogDetail(AppServer, "\tInstance Name: %s", mAttributes.instanceName); + } + if (mAttributes.numIPs > 0) + { + ChipLogDetail(AppServer, "\tNumber of IPs: %u", mAttributes.numIPs); + } + char buf[chip::Inet::IPAddress::kMaxStringLength]; + if (strlen(mAttributes.ipAddresses[0].ToString(buf)) != 0) + { + for (unsigned j = 0; j < mAttributes.numIPs; j++) + { + char * ipAddressOut = mAttributes.ipAddresses[j].ToString(buf); + ChipLogDetail(AppServer, "\tIP Address #%d: %s", j + 1, ipAddressOut); + } + } + if (mAttributes.port > 0) + { + ChipLogDetail(AppServer, "\tPort: %u", mAttributes.port); + } + if (mAttributes.productId > 0) + { + ChipLogDetail(AppServer, "\tProduct ID: %u", mAttributes.productId); + } + if (mAttributes.vendorId > 0) + { + ChipLogDetail(AppServer, "\tVendor ID: %u", mAttributes.vendorId); + } + if (mAttributes.deviceType > 0) + { + ChipLogDetail(AppServer, "\tDevice Type: %" PRIu32, mAttributes.deviceType); + } + if (mAttributes.nodeId > 0) + { + ChipLogDetail(AppServer, "\tNode ID: 0x" ChipLogFormatX64, ChipLogValueX64(mAttributes.nodeId)); + } + if (mAttributes.fabricIndex > 0) + { + ChipLogDetail(AppServer, "\tFabric Index: %u", mAttributes.fabricIndex); + } +} + +ConnectionContext::ConnectionContext(void * clientContext, core::CastingPlayer * targetCastingPlayer, + chip::OnDeviceConnected onDeviceConnectedFn, + chip::OnDeviceConnectionFailure onDeviceConnectionFailureFn) +{ + mClientContext = clientContext; + mTargetCastingPlayer = targetCastingPlayer; + mOnDeviceConnectedFn = onDeviceConnectedFn; + mOnDeviceConnectionFailureFn = onDeviceConnectionFailureFn; + + mOnConnectedCallback = new chip::Callback::Callback( + [](void * context, chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle) { + ChipLogProgress(AppServer, "Device Connection success callback called"); + ConnectionContext * connectionContext = static_cast(context); + VerifyOrReturn(connectionContext != nullptr && connectionContext->mTargetCastingPlayer != nullptr, + ChipLogError(AppServer, "Invalid ConnectionContext received in DeviceConnection success callback")); + + connectionContext->mTargetCastingPlayer->mConnectionState = core::CASTING_PLAYER_CONNECTED; + connectionContext->mOnDeviceConnectedFn(context, exchangeMgr, sessionHandle); + delete connectionContext; + }, + this); + + mOnConnectionFailureCallback = new chip::Callback::Callback( + [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { + ChipLogError(AppServer, "Device Connection failure callback called with %" CHIP_ERROR_FORMAT, error.Format()); + ConnectionContext * connectionContext = static_cast(context); + VerifyOrReturn(connectionContext != nullptr && connectionContext->mTargetCastingPlayer != nullptr, + ChipLogError(AppServer, "Invalid ConnectionContext received in DeviceConnection failure callback")); + connectionContext->mTargetCastingPlayer->mConnectionState = CASTING_PLAYER_NOT_CONNECTED; + connectionContext->mOnDeviceConnectionFailureFn(connectionContext->mClientContext, peerId, error); + delete connectionContext; + }, + this); +} + +ConnectionContext::~ConnectionContext() +{ + if (mOnConnectedCallback != nullptr) + { + delete mOnConnectedCallback; + } + + if (mOnConnectionFailureCallback != nullptr) + { + delete mOnConnectionFailureCallback; + } +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h index 977642790e60e6..17d722d6dc5210 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h @@ -19,8 +19,9 @@ #pragma once #include "Types.h" -#include "lib/support/logging/CHIPLogging.h" +#include "support/ChipDeviceEventHandler.h" +#include "lib/support/logging/CHIPLogging.h" #include #include #include @@ -30,47 +31,87 @@ namespace matter { namespace casting { namespace core { -enum ConnectionError -{ - NO_CONNECTION_ERROR = 0, - FAILED_TO_CONNECT = 1 -}; - -using ConnectCallback = std::function; -using DisconnectCallback = std::function; - const int kPortMaxLength = 5; // port is uint16_t // +1 for the : between the hostname and the port. -const int kIdMaxLength = chip::Dnssd::kHostNameMaxLength + kPortMaxLength + 1; +const int kIdMaxLength = chip::Dnssd::kHostNameMaxLength + kPortMaxLength + 1; +const unsigned long long int kCommissioningWindowTimeoutSec = 3 * 60; // 3 minutes class CastingPlayerAttributes { public: - char id[kIdMaxLength + 1] = {}; - char deviceName[chip::Dnssd::kMaxDeviceNameLen + 1] = {}; - char hostName[chip::Dnssd::kHostNameMaxLength + 1] = {}; - char instanceName[chip::Dnssd::kHostNameMaxLength + 1] = {}; + char id[kIdMaxLength + 1] = {}; + char deviceName[chip::Dnssd::kMaxDeviceNameLen + 1] = {}; + char hostName[chip::Dnssd::kHostNameMaxLength + 1] = {}; + char instanceName[chip::Dnssd::Commission::kInstanceNameMaxLength + 1] = {}; unsigned int numIPs; // number of valid IP addresses chip::Inet::IPAddress ipAddresses[chip::Dnssd::CommonResolutionData::kMaxIPAddresses]; + chip::Inet::InterfaceId interfaceId; uint16_t port; uint16_t productId; uint16_t vendorId; uint32_t deviceType; + + chip::NodeId nodeId = 0; + chip::FabricIndex fabricIndex = 0; +}; + +/** + * @brief Represents CastingPlayer ConnectionState. + * + */ +enum ConnectionState +{ + CASTING_PLAYER_NOT_CONNECTED, + CASTING_PLAYER_CONNECTING, + CASTING_PLAYER_CONNECTED, }; +class ConnectionContext; +class CastingPlayer; +using ConnectCallback = std::function; + /** * @brief CastingPlayer represents a Matter commissioner that is able to play media to a physical * output or to a display screen which is part of the device. */ class CastingPlayer : public std::enable_shared_from_this { -private: - // std::vector> endpoints; - bool mConnected = false; - CastingPlayerAttributes mAttributes; - public: CastingPlayer(CastingPlayerAttributes playerAttributes) { mAttributes = playerAttributes; } + + /** + * @brief Get the CastingPlayer object targeted currently (may not be connected) + */ + static CastingPlayer * GetTargetCastingPlayer() { return mTargetCastingPlayer; } + + /** + * @brief Compares based on the Id + */ + bool operator==(const CastingPlayer & other) const + { + int compareResult = strcmp(this->mAttributes.id, other.mAttributes.id); + return (compareResult == 0) ? 1 : 0; + } + + /** + * @return true if this CastingPlayer is connected to the CastingApp + */ + bool IsConnected() const { return mConnectionState == CASTING_PLAYER_CONNECTED; } + + /** + * @brief Verifies that a connection exists with this CastingPlayer, or triggers a new session + * request. If the CastingApp does not have the nodeId and fabricIndex of this CastingPlayer cached on disk, + * this will execute the user directed commissioning process. + * + * @param onCompleted for success - called back with CHIP_NO_ERROR and CastingPlayer *. + * For failure - called back with an error and nullptr. + * @param commissioningWindowTimeoutSec time (in sec) to keep the commissioning window open, if commissioning is required. + * Defaults to kCommissioningWindowTimeoutSec. + */ + void VerifyOrEstablishConnection(ConnectCallback onCompleted, + unsigned long long int commissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec); + void LogDetail() const; + const char * GetId() const { return mAttributes.id; } const char * GetDeviceName() const { return mAttributes.deviceName; } @@ -91,77 +132,77 @@ class CastingPlayer : public std::enable_shared_from_this uint32_t GetDeviceType() const { return mAttributes.deviceType; } - // public: + chip::NodeId GetNodeId() const { return mAttributes.nodeId; } + + chip::FabricIndex GetFabricIndex() const { return mAttributes.fabricIndex; } + + void SetNodeId(chip::NodeId nodeId) { mAttributes.nodeId = nodeId; } + + void SetFabricIndex(chip::FabricIndex fabricIndex) { mAttributes.fabricIndex = fabricIndex; } + // void RegisterEndpoint(const memory::Strong endpoint) { endpoints.push_back(endpoint); } // const std::vector> GetEndpoints() const { return endpoints; } +private: + // std::vector> endpoints; + ConnectionState mConnectionState = CASTING_PLAYER_NOT_CONNECTED; + CastingPlayerAttributes mAttributes; + static CastingPlayer * mTargetCastingPlayer; + unsigned long long int mCommissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec; + ConnectCallback mOnCompleted = {}; + +#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT /** - * @brief Compares based on the Id + * @brief Sends the user directed commissioning request to this CastingPlayer */ - bool operator==(const CastingPlayer & other) const - { - int compareResult = strcmp(this->mAttributes.id, other.mAttributes.id); - return (compareResult == 0) ? 1 : 0; - } + CHIP_ERROR SendUserDirectedCommissioningRequest(); -public: /** - * @return true if this CastingPlayer is connected to the CastingApp + * @brief Selects an IP Address to send the UDC request to. + * Prioritizes IPV4 addresses over IPV6. + * + * @return chip::Inet::IPAddress* */ - bool IsConnected() const { return mConnected; } + chip::Inet::IPAddress * GetIpAddressForUDCRequest(); +#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT - void Connect(const long timeout, ConnectCallback onCompleted); - void Disconnect(const long timeout, DisconnectCallback onCompleted); + /** + * @brief Find an existing session for this CastingPlayer, or trigger a new session + * request. + * + * The caller can optionally provide `onDeviceConnected` and `onDeviceConnectionFailure` callback + * objects. If provided, these will be used to inform the caller about + * successful or failed connection establishment. + * + * If the connection is already established, the `onDeviceConnected` callback + * will be immediately called, before FindOrEstablishSession returns. + * + * The `onDeviceConnectionFailure` callback may be called before the FindOrEstablishSession + * call returns, for error cases that are detected synchronously. + */ + void FindOrEstablishSession(void * clientContext, chip::OnDeviceConnected onDeviceConnected, + chip::OnDeviceConnectionFailure onDeviceConnectionFailure); - void LogDetail() const - { - if (strlen(mAttributes.id) != 0) - { - ChipLogDetail(Discovery, "\tID: %s", mAttributes.id); - } - if (strlen(mAttributes.deviceName) != 0) - { - ChipLogDetail(Discovery, "\tName: %s", mAttributes.deviceName); - } - if (strlen(mAttributes.hostName) != 0) - { - ChipLogDetail(Discovery, "\tHost Name: %s", mAttributes.hostName); - } - if (strlen(mAttributes.instanceName) != 0) - { - ChipLogDetail(Discovery, "\tInstance Name: %s", mAttributes.instanceName); - } - if (mAttributes.numIPs > 0) - { - ChipLogDetail(Discovery, "\tNumber of IPs: %u", mAttributes.numIPs); - } - char buf[chip::Inet::IPAddress::kMaxStringLength]; - if (strlen(mAttributes.ipAddresses[0].ToString(buf)) != 0) - { - for (unsigned j = 0; j < mAttributes.numIPs; j++) - { - char * ipAddressOut = mAttributes.ipAddresses[j].ToString(buf); - ChipLogDetail(AppServer, "\tIP Address #%d: %s", j + 1, ipAddressOut); - } - } - if (mAttributes.port > 0) - { - ChipLogDetail(Discovery, "\tPort: %u", mAttributes.port); - } - if (mAttributes.productId > 0) - { - ChipLogDetail(Discovery, "\tProduct ID: %u", mAttributes.productId); - } - if (mAttributes.vendorId > 0) - { - ChipLogDetail(Discovery, "\tVendor ID: %u", mAttributes.vendorId); - } - if (mAttributes.deviceType > 0) - { - ChipLogDetail(Discovery, "\tDevice Type: %" PRIu32, mAttributes.deviceType); - } - } + // ChipDeviceEventHandler handles chip::DeviceLayer::ChipDeviceEvent events and helps the CastingPlayer class commission with + // and connect to a CastingPlayer + friend class support::ChipDeviceEventHandler; + friend class ConnectionContext; +}; + +class ConnectionContext +{ +public: + ConnectionContext(void * clientContext, core::CastingPlayer * targetCastingPlayer, chip::OnDeviceConnected onDeviceConnected, + chip::OnDeviceConnectionFailure onDeviceConnectionFailure); + ~ConnectionContext(); + + void * mClientContext = nullptr; + core::CastingPlayer * mTargetCastingPlayer = nullptr; + chip::OnDeviceConnected mOnDeviceConnectedFn = nullptr; + chip::OnDeviceConnectionFailure mOnDeviceConnectionFailureFn = nullptr; + chip::Callback::Callback * mOnConnectedCallback = nullptr; + chip::Callback::Callback * mOnConnectionFailureCallback = nullptr; }; }; // namespace core diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp index e391116c0ef1f7..27bbe9bc14204b 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp @@ -79,24 +79,23 @@ void DeviceDiscoveryDelegateImpl::OnDiscoveredDevice(const chip::Dnssd::Discover // convert nodeData to CastingPlayer CastingPlayerAttributes attributes; - strcpy(attributes.id, nodeData.resolutionData.hostName); + snprintf(attributes.id, kIdMaxLength + 1, "%s%u", nodeData.resolutionData.hostName, nodeData.resolutionData.port); - char port[kPortMaxLength + 1] = {}; - snprintf(port, sizeof(port), "%u", nodeData.resolutionData.port); - strcat(attributes.id, port); + chip::Platform::CopyString(attributes.deviceName, chip::Dnssd::kMaxDeviceNameLen + 1, nodeData.commissionData.deviceName); + chip::Platform::CopyString(attributes.hostName, chip::Dnssd::kHostNameMaxLength + 1, nodeData.resolutionData.hostName); + chip::Platform::CopyString(attributes.instanceName, chip::Dnssd::Commission::kInstanceNameMaxLength + 1, + nodeData.commissionData.instanceName); - strcpy(attributes.deviceName, nodeData.commissionData.deviceName); - strcpy(attributes.hostName, nodeData.resolutionData.hostName); - strcpy(attributes.instanceName, nodeData.commissionData.instanceName); attributes.numIPs = (unsigned int) nodeData.resolutionData.numIPs; for (unsigned j = 0; j < attributes.numIPs; j++) { attributes.ipAddresses[j] = nodeData.resolutionData.ipAddress[j]; } - attributes.port = nodeData.resolutionData.port; - attributes.productId = nodeData.commissionData.productId; - attributes.vendorId = nodeData.commissionData.vendorId; - attributes.deviceType = nodeData.commissionData.deviceType; + attributes.interfaceId = nodeData.resolutionData.interfaceId; + attributes.port = nodeData.resolutionData.port; + attributes.productId = nodeData.commissionData.productId; + attributes.vendorId = nodeData.commissionData.vendorId; + attributes.deviceType = nodeData.commissionData.deviceType; memory::Strong player = std::make_shared(attributes); diff --git a/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp b/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp new file mode 100644 index 00000000000000..d8cdfca07ccaed --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp @@ -0,0 +1,343 @@ +/* + * + * 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 "CastingStore.h" + +#include +#include + +namespace matter { +namespace casting { +namespace support { + +CastingStore * CastingStore::_CastingStore = nullptr; + +CastingStore::CastingStore() {} + +CastingStore * CastingStore::GetInstance() +{ + if (_CastingStore == nullptr) + { + _CastingStore = new CastingStore(); + } + return _CastingStore; +} + +CHIP_ERROR CastingStore::AddOrUpdate(core::CastingPlayer castingPlayer) +{ + ChipLogProgress(AppServer, "CastingStore::AddOrUpdate"); + + // Read cache of CastingPlayers + std::vector castingPlayers = ReadAll(); + + // search for castingPlayer in CastingStore cache and overwrite it, if found + if (castingPlayers.size() != 0) + { + auto it = std::find_if( + castingPlayers.begin(), castingPlayers.end(), + [castingPlayer](const core::CastingPlayer & castingPlayerParam) { return castingPlayerParam == castingPlayer; }); + + if (it != castingPlayers.end()) + { + unsigned index = (unsigned int) std::distance(castingPlayers.begin(), it); + castingPlayers[index] = castingPlayer; + ChipLogProgress(AppServer, "CastingStore::AddOrUpdate updating CastingPlayer in CastingStore cache"); + return WriteAll(castingPlayers); // return early + } + } + + // add *new* castingPlayer to CastingStore cache + castingPlayers.push_back(castingPlayer); + ChipLogProgress(AppServer, "CastingStore::AddOrUpdate adding new CastingPlayer in CastingStore cache"); + return WriteAll(castingPlayers); +} + +std::vector CastingStore::ReadAll() +{ + ChipLogProgress(AppServer, "CastingStore::ReadAll called"); + CHIP_ERROR err = CHIP_NO_ERROR; + + std::vector castingPlayers; + uint8_t castingStoreData[kCastingStoreDataMaxBytes]; + size_t castingStoreDataSize = 0; + err = chip::DeviceLayer::PersistedStorage::KeyValueStoreMgr().Get(kCastingStoreDataKey, castingStoreData, + kCastingStoreDataMaxBytes, &castingStoreDataSize); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "KeyValueStoreMgr.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + ChipLogProgress(AppServer, "CastingStore::ReadAll Read TLV(CastingStoreData) from KVS store with size: %lu bytes", + static_cast(castingStoreDataSize)); + + chip::TLV::TLVReader reader; + reader.Init(castingStoreData); + + // read the envelope (and version) + err = reader.Next(chip::TLV::kTLVType_Structure, chip::TLV::AnonymousTag()); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Next failed %" CHIP_ERROR_FORMAT, err.Format())); + + chip::TLV::TLVType outerContainerType = chip::TLV::kTLVType_Structure; + err = reader.EnterContainer(outerContainerType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + err = reader.Next(); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Next failed %" CHIP_ERROR_FORMAT, err.Format())); + chip::TLV::Tag outerContainerTag = reader.GetTag(); + uint8_t outerContainerTagTagNum = static_cast(chip::TLV::TagNumFromTag(outerContainerTag)); + VerifyOrReturnValue(outerContainerTagTagNum == kCastingStoreDataVersionTag, castingPlayers, + ChipLogError(AppServer, "CastingStoreDataVersionTag not found")); + uint32_t version; + err = reader.Get(version); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + ChipLogProgress(AppServer, "CastingStore::ReadAll TLV(CastingStoreData) version: %d", version); + + // Entering CastingPlayers container + chip::TLV::TLVType castingPlayersContainerType = chip::TLV::kTLVType_Array; + err = reader.Next(); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Next failed %" CHIP_ERROR_FORMAT, err.Format())); + err = reader.EnterContainer(castingPlayersContainerType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + // Entering CastingPlayer container + chip::TLV::TLVType castingPlayerContainerType = chip::TLV::kTLVType_Structure; + err = reader.EnterContainer(castingPlayerContainerType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + core::CastingPlayerAttributes attributes; + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + chip::TLV::Tag castingPlayerContainerTag = reader.GetTag(); + VerifyOrReturnValue(chip::TLV::IsContextTag(castingPlayerContainerTag), std::vector(), + ChipLogError(AppServer, "Unexpected non-context TLV tag")); + + uint8_t castingPlayerContainerTagNum = static_cast(chip::TLV::TagNumFromTag(castingPlayerContainerTag)); + if (castingPlayerContainerTagNum == kCastingPlayerIdTag) + { + err = reader.GetBytes(reinterpret_cast(attributes.id), core::kIdMaxLength + 1); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.GetBytes failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (castingPlayerContainerTagNum == kCastingPlayerNodeIdTag) + { + err = reader.Get(attributes.nodeId); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (castingPlayerContainerTagNum == kCastingPlayerFabricIndexTag) + { + err = reader.Get(attributes.fabricIndex); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (castingPlayerContainerTagNum == kCastingPlayerVendorIdTag) + { + err = reader.Get(attributes.vendorId); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (castingPlayerContainerTagNum == kCastingPlayerProductIdTag) + { + err = reader.Get(attributes.productId); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (castingPlayerContainerTagNum == kCastingPlayerDeviceTypeIdTag) + { + err = reader.Get(attributes.deviceType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (castingPlayerContainerTagNum == kCastingPlayerDeviceNameTag) + { + err = reader.GetBytes(reinterpret_cast(attributes.deviceName), chip::Dnssd::kMaxDeviceNameLen + 1); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.GetBytes failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (castingPlayerContainerTagNum == kCastingPlayerHostNameTag) + { + err = reader.GetBytes(reinterpret_cast(attributes.hostName), chip::Dnssd::kHostNameMaxLength + 1); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.GetBytes failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (err == CHIP_END_OF_TLV) + { + // Exiting CastingPlayer container + err = reader.ExitContainer(castingPlayerContainerType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + core::CastingPlayer castingPlayer(attributes); + castingPlayers.push_back(castingPlayer); + break; + } + } + } + + VerifyOrReturnValue(err == CHIP_END_OF_TLV, std::vector(), + ChipLogError(AppServer, "TLV parsing failed %" CHIP_ERROR_FORMAT, err.Format())); + + err = reader.ExitContainer(castingPlayersContainerType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + err = reader.ExitContainer(outerContainerType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + ChipLogProgress(AppServer, "CastingStore::ReadAll CastingPlayers size: %lu", static_cast(castingPlayers.size())); + return castingPlayers; +} + +CHIP_ERROR CastingStore::DeleteAll() +{ + ChipLogProgress(AppServer, "CastingStore::DeleteAll called"); + CHIP_ERROR err = chip::DeviceLayer::PersistedStorage::KeyValueStoreMgr().Delete(kCastingStoreDataKey); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) // no error, if the key-value pair was not stored + { + ChipLogProgress(AppServer, "CastingStore::DeleteAll ignoring error %" CHIP_ERROR_FORMAT, err.Format()); + return CHIP_NO_ERROR; + } + return err; +} + +CHIP_ERROR CastingStore::Delete(core::CastingPlayer castingPlayer) +{ + ChipLogProgress(AppServer, "CastingStore::Delete"); + + // Read cache of CastingPlayers + std::vector castingPlayers = ReadAll(); + + // search for castingPlayer in CastingStore cache and delete it, if found + if (castingPlayers.size() != 0) + { + auto it = std::find_if( + castingPlayers.begin(), castingPlayers.end(), + [castingPlayer](const core::CastingPlayer & castingPlayerParam) { return castingPlayerParam == castingPlayer; }); + + if (it != castingPlayers.end()) + { + ChipLogProgress(AppServer, "CastingStore::Delete deleting CastingPlayer %s from CastingStore cache", it->GetId()); + castingPlayers.erase(it); + return WriteAll(castingPlayers); + } + } + return CHIP_NO_ERROR; +} + +void CastingStore::OnFabricRemoved(const chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) +{ + ChipLogProgress(AppServer, "CastingStore::OnFabricRemoved"); + + // Read cache of CastingPlayers + std::vector castingPlayers = ReadAll(); + + // search for castingPlayer in CastingStore cache and delete it, if found + if (castingPlayers.size() != 0) + { + auto it = std::find_if(castingPlayers.begin(), castingPlayers.end(), + [fabricIndex](const core::CastingPlayer & castingPlayerParam) { + return castingPlayerParam.GetFabricIndex() == fabricIndex; + }); + + if (it != castingPlayers.end()) + { + ChipLogProgress(AppServer, "CastingStore::OnFabricRemoved deleting CastingPlayer %s from CastingStore cache", + it->GetId()); + castingPlayers.erase(it); + WriteAll(castingPlayers); + } + } +} + +CHIP_ERROR CastingStore::WriteAll(std::vector castingPlayers) +{ + ChipLogProgress(AppServer, "CastingStore::WriteAll called"); + + chip::TLV::TLVWriter tlvWriter; + uint8_t castingStoreData[kCastingStoreDataMaxBytes]; + tlvWriter.Init(castingStoreData, kCastingStoreDataMaxBytes); + + chip::TLV::TLVType outerContainerType = chip::TLV::kTLVType_Structure; + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::AnonymousTag(), chip::TLV::kTLVType_Structure, outerContainerType)); + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingStoreDataVersionTag), kCurrentCastingStoreDataVersion)); + + chip::TLV::TLVType castingPlayersContainerType = chip::TLV::kTLVType_Array; + // CastingPlayers container starts + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayersContainerTag), chip::TLV::kTLVType_Array, + castingPlayersContainerType)); + + for (auto & castingPlayer : castingPlayers) + { + chip::TLV::TLVType castingPlayerContainerType = chip::TLV::kTLVType_Structure; + // CastingPlayer container starts + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerContainerTag), + chip::TLV::kTLVType_Structure, castingPlayerContainerType)); + + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerIdTag), castingPlayer.GetId())); + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerNodeIdTag), castingPlayer.GetNodeId())); + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerFabricIndexTag), castingPlayer.GetFabricIndex())); + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerVendorIdTag), castingPlayer.GetVendorId())); + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerProductIdTag), castingPlayer.GetProductId())); + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerDeviceTypeIdTag), castingPlayer.GetDeviceType())); + ReturnErrorOnFailure(tlvWriter.PutBytes(chip::TLV::ContextTag(kCastingPlayerDeviceNameTag), + (const uint8_t *) castingPlayer.GetDeviceName(), + static_cast(strlen(castingPlayer.GetDeviceName()) + 1))); + ReturnErrorOnFailure(tlvWriter.PutBytes(chip::TLV::ContextTag(kCastingPlayerHostNameTag), + (const uint8_t *) castingPlayer.GetHostName(), + static_cast(strlen(castingPlayer.GetHostName()) + 1))); + // CastingPlayer container ends + ReturnErrorOnFailure(tlvWriter.EndContainer(castingPlayerContainerType)); + } + + // CastingPlayers container ends + ReturnErrorOnFailure(tlvWriter.EndContainer(castingPlayersContainerType)); + ReturnErrorOnFailure(tlvWriter.EndContainer(outerContainerType)); + + ReturnErrorOnFailure(tlvWriter.Finalize()); + ChipLogProgress(AppServer, + "CastingStore::WriteAll TLV(CastingStoreData).LengthWritten: %d bytes, CastingPlayers size: %lu " + "and version: %d", + tlvWriter.GetLengthWritten(), castingPlayers.size(), kCurrentCastingStoreDataVersion); + return chip::DeviceLayer::PersistedStorage::KeyValueStoreMgr().Put(kCastingStoreDataKey, castingStoreData, + tlvWriter.GetLengthWritten()); +} + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/support/CastingStore.h b/examples/tv-casting-app/tv-casting-common/support/CastingStore.h new file mode 100644 index 00000000000000..3d8ec7f613511b --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/support/CastingStore.h @@ -0,0 +1,97 @@ +/* + * + * 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 "core/CastingPlayer.h" + +namespace matter { +namespace casting { +namespace support { + +/** + * @brief CastingStore uses TLV-encoding to cache casting related data on disk, like the list of CastingPlayers that the + * tv-casting-app previously connected to. + */ +class CastingStore : public chip::FabricTable::Delegate +{ +public: + static CastingStore * GetInstance(); + + /** + * @brief If castingPlayer already exists in the cache on disk (compares by castingPlayer.id), this will update the record for + * it in the cache. If castingPlayer is new i.e. not found, this will add it to the cache + */ + CHIP_ERROR AddOrUpdate(core::CastingPlayer castingPlayer); + + /** + * @brief Reads and returns a vector of all CastingPlayers found in the cache + */ + std::vector ReadAll(); + + /** + * @brief If castingPlayer is found in the cache, this will delete it. If it is not found, this method is a no-op + */ + CHIP_ERROR Delete(core::CastingPlayer castingPlayer); + + /** + * @brief Purges the cache of all CastingStore data + */ + CHIP_ERROR DeleteAll(); + + /** + * @brief Implements chip::FabricTable::Delegate.OnFabricRemoved. Searches for and deletes the CastingPlayer(s) that match(es) + * the fabrixIndex argument, if any. If no such CastingPlayer exists, this method is a no-op. + */ + void OnFabricRemoved(const chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) override; + +private: + CastingStore(); + static CastingStore * _CastingStore; + + /** + * @brief Writes the vector of CastingPlayers to the cache. This method will overwrite any pre-existing cached data. + */ + CHIP_ERROR WriteAll(std::vector castingPlayers); + + enum CastingStoreTLVTag + { + kCastingStoreDataVersionTag = 1, + kCastingPlayersContainerTag, + kCastingPlayerContainerTag, + kCastingPlayerIdTag, + kCastingPlayerNodeIdTag, + kCastingPlayerFabricIndexTag, + kCastingPlayerVendorIdTag, + kCastingPlayerProductIdTag, + kCastingPlayerDeviceTypeIdTag, + kCastingPlayerDeviceNameTag, + kCastingPlayerHostNameTag, + + kContextTagMaxNum = UINT8_MAX + }; + + constexpr static size_t kCastingStoreDataMaxBytes = 1024 * 100; // 100 KBs + constexpr static char * kCastingStoreDataKey = (char *) "com.matter.casting.CastingStore"; + constexpr static uint32_t kCurrentCastingStoreDataVersion = 1; + constexpr static uint32_t kSupportedCastingStoreDataVersions[1] = { 1 }; +}; + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp new file mode 100644 index 00000000000000..704e4ef7295874 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp @@ -0,0 +1,189 @@ +/* + * + * 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 "ChipDeviceEventHandler.h" + +#include "core/CastingPlayer.h" +#include "core/Types.h" +#include "support/CastingStore.h" + +#include "app/clusters/bindings/BindingManager.h" + +namespace matter { +namespace casting { +namespace support { + +using namespace matter::casting::core; + +bool ChipDeviceEventHandler::sUdcInProgress = false; + +void ChipDeviceEventHandler::Handle(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg) +{ + ChipLogProgress(AppServer, "ChipDeviceEventHandler::Handle called"); + + bool runPostCommissioning = false; + chip::NodeId targetNodeId = 0; + chip::FabricIndex targetFabricIndex = 0; + + if (event->Type == chip::DeviceLayer::DeviceEventType::kFailSafeTimerExpired && + CastingPlayer::GetTargetCastingPlayer()->mConnectionState == CASTING_PLAYER_CONNECTING) + { + HandleFailSafeTimerExpired(); + } + else if (event->Type == chip::DeviceLayer::DeviceEventType::kBindingsChangedViaCluster && + CastingPlayer::GetTargetCastingPlayer() != nullptr) + { + HandleBindingsChangedViaCluster(event, arg, runPostCommissioning, targetNodeId, targetFabricIndex); + } + else if (event->Type == chip::DeviceLayer::DeviceEventType::kCommissioningComplete) + { + HandleCommissioningComplete(event, arg, runPostCommissioning, targetNodeId, targetFabricIndex); + } + + if (runPostCommissioning) + { + sUdcInProgress = false; + CastingPlayer::GetTargetCastingPlayer()->SetNodeId(targetNodeId); + CastingPlayer::GetTargetCastingPlayer()->SetFabricIndex(targetFabricIndex); + + CastingPlayer::GetTargetCastingPlayer()->FindOrEstablishSession( + nullptr, + [](void * context, chip::Messaging::ExchangeManager & exchangeMgr, const chip::SessionHandle & sessionHandle) { + ChipLogProgress(AppServer, "ChipDeviceEventHandler::Handle: Connection to CastingPlayer successful"); + CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_CONNECTED; + support::CastingStore::GetInstance()->AddOrUpdate(*CastingPlayer::GetTargetCastingPlayer()); + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(CHIP_NO_ERROR, CastingPlayer::GetTargetCastingPlayer()); + }, + [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { + ChipLogError(AppServer, "ChipDeviceEventHandler::Handle: Connection to CastingPlayer failed"); + CastingPlayer::GetTargetCastingPlayer()->mConnectionState = CASTING_PLAYER_NOT_CONNECTED; + support::CastingStore::GetInstance()->Delete(*CastingPlayer::GetTargetCastingPlayer()); + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(error, nullptr); + CastingPlayer::mTargetCastingPlayer = nullptr; + }); + } +} + +void ChipDeviceEventHandler::HandleFailSafeTimerExpired() +{ + ChipLogProgress(AppServer, "ChipDeviceEventHandler::HandleFailSafeTimerExpired called"); + chip::DeviceLayer::SystemLayer().StartTimer( + chip::System::Clock::Milliseconds32(1), + [](chip::System::Layer * aSystemLayer, void * aAppState) { + ChipLogProgress(AppServer, "ChipDeviceEventHandler::Handle running OpenBasicCommissioningWindow"); + CHIP_ERROR err = chip::Server::GetInstance().GetCommissioningWindowManager().OpenBasicCommissioningWindow( + chip::System::Clock::Seconds16(CastingPlayer::GetTargetCastingPlayer()->mCommissioningWindowTimeoutSec)); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "ChipDeviceEventHandler::Handle Failed to OpenBasicCommissioningWindow %" CHIP_ERROR_FORMAT, + err.Format()); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(err, nullptr); + return; + } + +#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT + err = CastingPlayer::GetTargetCastingPlayer()->SendUserDirectedCommissioningRequest(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, + "ChipDeviceEventHandler::Handle Failed to SendUserDirectedCommissioningRequest %" CHIP_ERROR_FORMAT, + err.Format()); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(err, nullptr); + return; + } +#endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT + }, + nullptr); +} + +void ChipDeviceEventHandler::HandleBindingsChangedViaCluster(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg, + bool & runPostCommissioning, chip::NodeId & targetNodeId, + chip::FabricIndex & targetFabricIndex) +{ + ChipLogProgress(AppServer, "ChipDeviceEventHandler::HandleBindingsChangedViaCluster called"); + + if (CastingPlayer::GetTargetCastingPlayer()->IsConnected()) + { + // re-use existing nodeId and fabricIndex + ChipLogProgress(AppServer, "ChipDeviceEventHandler::HandleBindingsChangedViaCluster already connected to video player"); + runPostCommissioning = true; + targetNodeId = CastingPlayer::GetTargetCastingPlayer()->GetNodeId(); + targetFabricIndex = CastingPlayer::GetTargetCastingPlayer()->GetFabricIndex(); + } + // handle the "re-commissioning" use case i.e. we received a kBindingsChangedViaCluster event when UDC was in progress (i.e. no + // kCommissioningComplete received) + else if (sUdcInProgress) + { + ChipLogProgress(AppServer, + "ChipDeviceEventHandler::HandleBindingsChangedViaCluster UDC is in progress while handling " + "kBindingsChangedViaCluster with " + "fabricIndex: %d", + event->BindingsChanged.fabricIndex); + sUdcInProgress = false; + + // find targetNodeId from binding table by matching the binding's fabricIndex with the accessing fabricIndex + // received in BindingsChanged event + for (const auto & binding : chip::BindingTable::GetInstance()) + { + ChipLogProgress(AppServer, + "ChipDeviceEventHandler::HandleBindingsChangedViaCluster Read cached binding type=%d fabrixIndex=%d " + "nodeId=0x" ChipLogFormatX64 + " groupId=%d local endpoint=%d remote endpoint=%d cluster=" ChipLogFormatMEI, + binding.type, binding.fabricIndex, ChipLogValueX64(binding.nodeId), binding.groupId, binding.local, + binding.remote, ChipLogValueMEI(binding.clusterId.ValueOr(0))); + if (binding.type == EMBER_UNICAST_BINDING && event->BindingsChanged.fabricIndex == binding.fabricIndex) + { + ChipLogProgress(AppServer, + "ChipDeviceEventHandler::HandleBindingsChangedViaCluster Matched accessingFabricIndex with " + "nodeId=0x" ChipLogFormatX64, + ChipLogValueX64(binding.nodeId)); + targetNodeId = binding.nodeId; + targetFabricIndex = binding.fabricIndex; + runPostCommissioning = true; + break; + } + } + + if (targetNodeId == 0 && runPostCommissioning == false) + { + ChipLogError(AppServer, + "ChipDeviceEventHandler::HandleBindingsChangedViaCluster accessingFabricIndex: %d did not match bindings", + event->BindingsChanged.fabricIndex); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(CHIP_ERROR_INCORRECT_STATE, + CastingPlayer::GetTargetCastingPlayer()); + return; + } + } +} + +void ChipDeviceEventHandler::HandleCommissioningComplete(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg, + bool & runPostCommissioning, chip::NodeId & targetNodeId, + chip::FabricIndex & targetFabricIndex) +{ + ChipLogProgress(AppServer, "ChipDeviceEventHandler::HandleCommissioningComplete called"); + sUdcInProgress = false; + targetNodeId = event->CommissioningComplete.nodeId; + targetFabricIndex = event->CommissioningComplete.fabricIndex; + runPostCommissioning = true; +} + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h new file mode 100644 index 00000000000000..69142603ce40f0 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h @@ -0,0 +1,84 @@ +/* + * + * 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 + +namespace matter { +namespace casting { +namespace support { + +/** + * @brief Handles chip::DeviceLayer::ChipDeviceEvent events (such as kFailSafeTimerExpired, kBindingsChangedViaCluster, + * kCommissioningComplete) sent by the Matter DeviceLayer. + * ChipDeviceEventHandlerhelps the CastingPlayer class commission with and connect to a CastingPlayer + */ +class ChipDeviceEventHandler +{ +public: + /** + * @brief Top level handler to handle chip::DeviceLayer::ChipDeviceEvent. Delegates to HandleBindingsChangedViaCluster and other + * handlers + */ + static void Handle(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg); + + /** + * @brief Sets internal ChipDeviceEventHandler state to indicate that User Directed Commissioning (UDC) is in progress. + * If UDC was already in progress when this method was called, it will return a CHIP_ERROR_INCORRECT_STATE without changing the + * internal state. + */ + static CHIP_ERROR SetUdcStatus(bool udcInProgress) + { + if (sUdcInProgress == udcInProgress) + { + ChipLogError(AppServer, "UDC in progress state is already %d", sUdcInProgress); + return CHIP_ERROR_INCORRECT_STATE; + } + sUdcInProgress = udcInProgress; + return CHIP_NO_ERROR; + } + +private: + /** + * @brief if kFailSafeTimerExpired is received and a request to connect to a CastingPlayer is pending, open a basic + * commissioning window and send the user directed commissioning request to the target CastingPlayer + */ + static void HandleFailSafeTimerExpired(); + + /** + * @brief Handles cases where the tv-casting-app is being "re-commissioned" from a CastingPlayer that it was previously + * commissioned by. + */ + static void HandleBindingsChangedViaCluster(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg, + bool & runPostCommissioning, chip::NodeId & targetPeerNodeId, + chip::FabricIndex & targetFabricIndex); + + /** + * @brief Handles kCommissioningComplete event which is received when commissioning completes + */ + static void HandleCommissioningComplete(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg, + bool & runPostCommissioning, chip::NodeId & targetPeerNodeId, + chip::FabricIndex & targetFabricIndex); + + static bool sUdcInProgress; +}; + +}; // namespace support +}; // namespace casting +}; // namespace matter