diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md index d7f41ac5b376c1..404c6d896bd118 100644 --- a/examples/tv-casting-app/APIs.md +++ b/examples/tv-casting-app/APIs.md @@ -38,8 +38,8 @@ Commissioner. In the context of the [Matter Video Player architecture](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/app_clusters/media/VideoPlayerArchitecture.adoc), a `CastingPlayer` would map to [Casting "Video" Player](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/app_clusters/media/VideoPlayerArchitecture.adoc#1-introduction). -The `CastingPlayer` is expected to be hosting one or more `Endpoints` (similar -to +The `CastingPlayer` is expected to be hosting one or more `Endpoints` (some of +which can represent [Content Apps](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/app_clusters/media/VideoPlayerArchitecture.adoc#1-introduction) in the Matter Video Player architecture) that support one or more Matter Media `Clusters`. @@ -73,7 +73,10 @@ The Casting Client is expected to consume the Matter TV Casting library built for its respective platform which implements the APIs described in this document. Refer to the tv-casting-app READMEs for [Linux](linux/README.md), Android and [iOS](darwin/TvCasting/README.md) to understand how to build and -consume each platform's specific libraries. +consume each platform's specific libraries. The libraries MUST be built with the +client's specific values for `CHIP_DEVICE_CONFIG_DEVICE_VENDOR_ID` and +`CHIP_DEVICE_CONFIG_DEVICE_PRODUCT_ID` updated in the +[CHIPProjectAppConfig.h](tv-casting-common/include/CHIPProjectAppConfig.h) file. ### Initialize the Casting Client @@ -474,11 +477,21 @@ 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`. +Optionally, the following arguments may also be passed in. The optional +`commissioningWindowTimeoutSec` indicates how long to keep the commissioning +window open, if commissioning is required. And `DesiredEndpointFilter` specifies +the attributes, such as Vendor ID and Product ID of the `Endpoint`, the Casting +client desires to interact with after connecting. This forces the Matter TV +Casting library to go through the full UDC process in search of the desired +Endpoint, in cases where it is not available in the Casting client's cache. On Linux, the Casting Client can connect to a `CastingPlayer` by successfully calling `VerifyOrEstablishConnection` on it. ```c + +const uint16_t kDesiredEndpointVendorId = 65521; + void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * castingPlayer) { ChipLogProgress(AppServer, "ConnectionHandler called with %" CHIP_ERROR_FORMAT, err.Format()); @@ -486,7 +499,11 @@ void ConnectionHandler(CHIP_ERROR err, matter::casting::core::CastingPlayer * ca ... // targetCastingPlayer is a discovered CastingPlayer -targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler); +matter::casting::core::EndpointFilter desiredEndpointFilter; +desiredEndpointFilter.vendorId = kDesiredEndpointVendorId; +targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler, + matter::casting::core::kCommissioningWindowTimeoutSec, + desiredEndpointFilter); ... ``` diff --git a/examples/tv-casting-app/linux/simple-app-helper.cpp b/examples/tv-casting-app/linux/simple-app-helper.cpp index dab134dfb7b57c..fe248c2a66d2a8 100644 --- a/examples/tv-casting-app/linux/simple-app-helper.cpp +++ b/examples/tv-casting-app/linux/simple-app-helper.cpp @@ -16,6 +16,8 @@ */ #include "simple-app-helper.h" +#include "clusters/ContentLauncherCluster.h" + #include "app/clusters/bindings/BindingManager.h" #include #include @@ -27,6 +29,9 @@ #include #include +// VendorId of the Endpoint on the CastingPlayer that the CastingApp desires to interact with after connection +const uint16_t kDesiredEndpointVendorId = 65521; + DiscoveryDelegateImpl * DiscoveryDelegateImpl::_discoveryDelegateImpl = nullptr; DiscoveryDelegateImpl * DiscoveryDelegateImpl::GetInstance() @@ -102,7 +107,11 @@ CHIP_ERROR CommandHandler(int argc, char ** argv) 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); + + matter::casting::core::EndpointFilter desiredEndpointFilter; + desiredEndpointFilter.vendorId = kDesiredEndpointVendorId; + targetCastingPlayer->VerifyOrEstablishConnection(ConnectionHandler, matter::casting::core::kCommissioningWindowTimeoutSec, + desiredEndpointFilter); return CHIP_NO_ERROR; } if (strcmp(argv[0], "print-bindings") == 0) diff --git a/examples/tv-casting-app/linux/simple-app-helper.h b/examples/tv-casting-app/linux/simple-app-helper.h index d48b082e0faf30..9b114337d2fff1 100644 --- a/examples/tv-casting-app/linux/simple-app-helper.h +++ b/examples/tv-casting-app/linux/simple-app-helper.h @@ -21,7 +21,6 @@ #include "core/CastingPlayer.h" #include "core/CastingPlayerDiscovery.h" #include "core/Types.h" - #include /** diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index c5ad03964cb0d9..697ae980e40c54 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -93,12 +93,18 @@ chip_data_model("tv-casting-common") { # Add simplified casting API files here sources += [ + "clusters/ContentLauncherCluster.h", + "clusters/MediaPlaybackCluster.h", + "clusters/TargetNavigatorCluster.h", + "core/Attribute.h", "core/CastingApp.cpp", "core/CastingApp.h", "core/CastingPlayer.cpp", "core/CastingPlayer.h", "core/CastingPlayerDiscovery.cpp", "core/CastingPlayerDiscovery.h", + "core/Cluster.h", + "core/Endpoint.h", "core/Types.h", "support/AppParameters.h", "support/CastingStore.cpp", @@ -106,6 +112,8 @@ chip_data_model("tv-casting-common") { "support/ChipDeviceEventHandler.cpp", "support/ChipDeviceEventHandler.h", "support/DataProvider.h", + "support/EndpointListLoader.cpp", + "support/EndpointListLoader.h", ] deps = [ diff --git a/examples/tv-casting-app/tv-casting-common/clusters/ContentLauncherCluster.h b/examples/tv-casting-app/tv-casting-common/clusters/ContentLauncherCluster.h new file mode 100644 index 00000000000000..a8d5ad847a003b --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/clusters/ContentLauncherCluster.h @@ -0,0 +1,44 @@ +/* + * + * 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/Endpoint.h" +#include "core/Types.h" + +#include "lib/support/logging/CHIPLogging.h" + +namespace matter { +namespace casting { +namespace clusters { + +class ContentLauncherCluster : public core::BaseCluster +{ +private: +protected: +public: + ContentLauncherCluster(memory::Weak endpoint) : core::BaseCluster(endpoint) {} + + // TODO: + // LaunchURL(const char * contentUrl, const char * contentDisplayStr, + // chip::Optional brandingInformation); +}; + +}; // namespace clusters +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/clusters/MediaPlaybackCluster.h b/examples/tv-casting-app/tv-casting-common/clusters/MediaPlaybackCluster.h new file mode 100644 index 00000000000000..8fe196597f001e --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/clusters/MediaPlaybackCluster.h @@ -0,0 +1,42 @@ +/* + * + * 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/Endpoint.h" +#include "core/Types.h" + +#include "lib/support/logging/CHIPLogging.h" + +namespace matter { +namespace casting { +namespace clusters { + +class MediaPlaybackCluster : public core::BaseCluster +{ +private: +protected: +public: + MediaPlaybackCluster(memory::Weak endpoint) : core::BaseCluster(endpoint) {} + + // TODO: add commands +}; + +}; // namespace clusters +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/clusters/TargetNavigatorCluster.h b/examples/tv-casting-app/tv-casting-common/clusters/TargetNavigatorCluster.h new file mode 100644 index 00000000000000..0f03e9296d73ca --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/clusters/TargetNavigatorCluster.h @@ -0,0 +1,42 @@ +/* + * + * 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/Endpoint.h" +#include "core/Types.h" + +#include "lib/support/logging/CHIPLogging.h" + +namespace matter { +namespace casting { +namespace clusters { + +class TargetNavigatorCluster : public core::BaseCluster +{ +private: +protected: +public: + TargetNavigatorCluster(memory::Weak endpoint) : core::BaseCluster(endpoint) {} + + // TODO: add commands +}; + +}; // namespace clusters +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/core/Attribute.h b/examples/tv-casting-app/tv-casting-common/core/Attribute.h new file mode 100644 index 00000000000000..5c8716967bab86 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/core/Attribute.h @@ -0,0 +1,76 @@ +/* + * + * 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 "Cluster.h" +#include "Types.h" + +#include "lib/support/logging/CHIPLogging.h" + +namespace matter { +namespace casting { +namespace core { + +enum ReadAttributeError +{ + READ_ATTRIBUTE_NO_ERROR +}; + +enum WriteAttributeError +{ + WRITE_ATTRIBUTE_NO_ERROR +}; + +template +using ReadAttributeCallback = std::function before, ValueType after, ReadAttributeError)>; + +using WriteAttributeCallback = std::function; + +class BaseCluster; + +template +class Attribute +{ +private: + memory::Weak cluster; + ValueType value; + +public: + Attribute(memory::Weak cluster) { this->cluster = cluster; } + + ~Attribute() {} + + Attribute() = delete; + Attribute(Attribute & other) = delete; + void operator=(const Attribute &) = delete; + +protected: + memory::Strong GetCluster() const { return cluster.lock(); } + +public: + ValueType GetValue(); + void Read(ReadAttributeCallback onRead); + void Write(ValueType value, WriteAttributeCallback onWrite); + bool SubscribeAttribute(AttributeId attributeId, ReadAttributeCallback callback); + bool UnsubscribeAttribute(AttributeId attributeId, ReadAttributeCallback callback); +}; + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp index 9cc11a774e1564..e0d2e22d7f1524 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.cpp @@ -17,6 +17,8 @@ */ #include "CastingPlayer.h" +#include "Endpoint.h" + #include "support/CastingStore.h" #include @@ -27,7 +29,8 @@ namespace core { CastingPlayer * CastingPlayer::mTargetCastingPlayer = nullptr; -void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, unsigned long long int commissioningWindowTimeoutSec) +void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, unsigned long long int commissioningWindowTimeoutSec, + EndpointFilter desiredEndpointFilter) { ChipLogProgress(AppServer, "CastingPlayer::VerifyOrEstablishConnection called"); @@ -46,40 +49,49 @@ void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, uns 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 *this* CastingPlayer was previously connected to, its nodeId, fabricIndex and other attributes should be present + // in the CastingStore cache. If that is the case, AND, the cached data contains the endpoint desired by the client, if any, + // as per desiredEndpointFilter, 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; }); + // found the CastingPlayer in cache 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. + if (ContainsDesiredEndpoint(&cachedCastingPlayers[index], desiredEndpointFilter)) + { + *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; + + // this async call will Load all the endpoints with their respective attributes into the TargetCastingPlayer + // persist the TargetCastingPlayer information into the CastingStore and call mOnCompleted() + support::EndpointListLoader::GetInstance()->Initialize(&exchangeMgr, &sessionHandle); + support::EndpointListLoader::GetInstance()->Load(); + }, + [](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 + // this CastingPlayer is not in the list of cached CastingPlayers previously connected to or the cached data + // does not contain the endpoint the client desires to interact with. So, this VerifyOrEstablishConnection call // will require User Directed Commissioning. if (chip::Server::GetInstance().GetFailSafeContext().IsFailSafeArmed()) { @@ -101,13 +113,31 @@ void CastingPlayer::VerifyOrEstablishConnection(ConnectCallback onCompleted, uns exit: if (err != CHIP_NO_ERROR) { + ChipLogError(AppServer, "CastingPlayer::VerifyOrEstablishConnection failed with %" CHIP_ERROR_FORMAT, err.Format()); 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); + mOnCompleted = nullptr; + } +} + +void CastingPlayer::RegisterEndpoint(const memory::Strong endpoint) +{ + auto it = std::find_if(mEndpoints.begin(), mEndpoints.end(), [endpoint](const memory::Strong & _endpoint) { + return _endpoint->GetId() == endpoint->GetId(); + }); + + // If existing endpoint, update mEndpoints. If new endpoint, add it to the vector mEndpoints + if (it != mEndpoints.end()) + { + unsigned index = (unsigned int) std::distance(mEndpoints.begin(), it); + mEndpoints[index] = endpoint; + } + else + { + mEndpoints.push_back(endpoint); } } @@ -165,6 +195,24 @@ void CastingPlayer::FindOrEstablishSession(void * clientContext, chip::OnDeviceC connectionContext->mOnConnectionFailureCallback); } +bool CastingPlayer::ContainsDesiredEndpoint(core::CastingPlayer * cachedCastingPlayer, EndpointFilter desiredEndpointFilter) +{ + std::vector> cachedEndpoints = cachedCastingPlayer->GetEndpoints(); + for (const auto & cachedEndpoint : cachedEndpoints) + { + bool match = true; + match = match && (desiredEndpointFilter.vendorId == 0 || cachedEndpoint->GetVendorId() == desiredEndpointFilter.vendorId); + match = + match && (desiredEndpointFilter.productId == 0 || cachedEndpoint->GetProductId() == desiredEndpointFilter.productId); + // TODO: check deviceTypeList + if (match) + { + return true; + } + } + return false; +} + void CastingPlayer::LogDetail() const { if (strlen(mAttributes.id) != 0) 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 17d722d6dc5210..60aba39b94c254 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h @@ -18,8 +18,10 @@ #pragma once +#include "Endpoint.h" #include "Types.h" #include "support/ChipDeviceEventHandler.h" +#include "support/EndpointListLoader.h" #include "lib/support/logging/CHIPLogging.h" #include @@ -36,6 +38,17 @@ const int kPortMaxLength = 5; // port is uint16_t const int kIdMaxLength = chip::Dnssd::kHostNameMaxLength + kPortMaxLength + 1; const unsigned long long int kCommissioningWindowTimeoutSec = 3 * 60; // 3 minutes +/** + * @brief Describes an Endpoint that the client wants to connect to + */ +struct EndpointFilter +{ + // value of 0 means unspecified + uint16_t vendorId = 0; + uint16_t productId = 0; + std::vector requiredDeviceTypes; +}; + class CastingPlayerAttributes { public: @@ -55,6 +68,8 @@ class CastingPlayerAttributes chip::FabricIndex fabricIndex = 0; }; +class Endpoint; + /** * @brief Represents CastingPlayer ConnectionState. * @@ -105,11 +120,24 @@ class CastingPlayer : public std::enable_shared_from_this * * @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. + * @param commissioningWindowTimeoutSec (Optional) time (in sec) to keep the commissioning window open, if commissioning is + * required. Defaults to kCommissioningWindowTimeoutSec. + * @param desiredEndpointFilter (Optional) Attributes (such as VendorId) describing an Endpoint that the client wants to + * interact with after commissioning. If this value is passed in, the VerifyOrEstablishConnection will force User Directed + * Commissioning, in case the desired Endpoint is not found in the on device CastingStore. */ void VerifyOrEstablishConnection(ConnectCallback onCompleted, - unsigned long long int commissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec); + unsigned long long int commissioningWindowTimeoutSec = kCommissioningWindowTimeoutSec, + EndpointFilter desiredEndpointFilter = EndpointFilter()); + + /** + * @brief Register an endpoint on this CastingPlayer. If the provided endpoint was already registered, its information will be + * updated in the registry. + */ + void RegisterEndpoint(const memory::Strong endpoint); + + const std::vector> GetEndpoints() const { return mEndpoints; } + void LogDetail() const; const char * GetId() const { return mAttributes.id; } @@ -140,12 +168,8 @@ class CastingPlayer : public std::enable_shared_from_this 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; + std::vector> mEndpoints; ConnectionState mConnectionState = CASTING_PLAYER_NOT_CONNECTED; CastingPlayerAttributes mAttributes; static CastingPlayer * mTargetCastingPlayer; @@ -184,10 +208,20 @@ class CastingPlayer : public std::enable_shared_from_this void FindOrEstablishSession(void * clientContext, chip::OnDeviceConnected onDeviceConnected, chip::OnDeviceConnectionFailure onDeviceConnectionFailure); + /** + * @brief Checks if the cachedCastingPlayer contains an Endpoint that matches the description of the desiredEndpointFilter + * + * @return true - cachedCastingPlayer contains at least one endpoint that matches all the (non-default) values in + * desiredEndpointFilter, false otherwise + */ + bool ContainsDesiredEndpoint(core::CastingPlayer * cachedCastingPlayer, EndpointFilter desiredEndpointFilter); + // 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; + friend class support::EndpointListLoader; }; class ConnectionContext diff --git a/examples/tv-casting-app/tv-casting-common/core/Cluster.h b/examples/tv-casting-app/tv-casting-common/core/Cluster.h new file mode 100644 index 00000000000000..678e55caf4237d --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/core/Cluster.h @@ -0,0 +1,54 @@ +/* + * + * 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 "Endpoint.h" +#include "Types.h" + +#include "lib/support/logging/CHIPLogging.h" + +namespace matter { +namespace casting { +namespace core { + +class Endpoint; + +// Base cluster class +class BaseCluster +{ +private: +protected: + memory::Weak mEndpoint; + +public: + BaseCluster(memory::Weak endpoint) { this->mEndpoint = endpoint; } + + virtual ~BaseCluster() {} + + BaseCluster() = delete; + BaseCluster(BaseCluster & other) = delete; + void operator=(const BaseCluster &) = delete; + +protected: + memory::Weak GetEndpoint() const { return mEndpoint.lock(); } +}; + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/core/Endpoint.h b/examples/tv-casting-app/tv-casting-common/core/Endpoint.h new file mode 100644 index 00000000000000..a481882b6de80e --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/core/Endpoint.h @@ -0,0 +1,149 @@ +/* + * + * 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 "CastingPlayer.h" +#include "Cluster.h" +#include "Types.h" + +#include "lib/support/logging/CHIPLogging.h" +#include + +#include +#include +#include +#include +#include + +namespace matter { +namespace casting { +namespace core { + +class EndpointAttributes +{ +public: + // value of 0 means the attribute could not be read for the corresponding Endpoint + chip::EndpointId mId = 0; + uint16_t mVendorId = 0; + uint16_t mProductId = 0; + std::vector mDeviceTypeList; +}; + +class CastingPlayer; + +/** + * @brief An Endpoint on a CastingPlayer e.g. a Speaker or a Matter Content App + */ +class Endpoint : public std::enable_shared_from_this +{ + +private: + CastingPlayer * mCastingPlayer; + + EndpointAttributes mAttributes; + std::map> mClusters; + +protected: + CastingPlayer * GetCastingPlayer() const { return mCastingPlayer; } + +public: + Endpoint(CastingPlayer * castingPlayer, const EndpointAttributes & attributes) + { + this->mCastingPlayer = castingPlayer; + this->mAttributes = attributes; + } + + ~Endpoint() {} + + Endpoint() = delete; + Endpoint(Endpoint & other) = delete; + void operator=(const Endpoint &) = delete; + + /** + * @brief Compares based on the Id + */ + bool operator==(const Endpoint & other) const { return this->mAttributes.mId == other.mAttributes.mId; } + + chip::EndpointId GetId() const { return mAttributes.mId; } + + /** + * @return uint16_t - value 0 indicates no ProductId was returned for this Endpoint + */ + uint16_t GetProductId() const { return mAttributes.mProductId; } + + /** + * @return uint16_t - value 0 indicates no VendorId was returned for this Endpoint + */ + uint16_t GetVendorId() const { return mAttributes.mVendorId; } + + /** + * @return uint16_t - empty vector indicates no DeviceTypeList was returned for this Endpoint + */ + std::vector GetDeviceTypeList() const + { + return mAttributes.mDeviceTypeList; + } + + /** + * @return uint16_t - empty vector indicates no ServerList was returned for this Endpoint + */ + std::vector GetServerList() + { + std::vector serverList; + for (auto const & cluster : mClusters) + { + serverList.push_back(cluster.first); + } + return serverList; + } + + /** + * @brief Registers a cluster of type T against the passed in clusterId + * for this Endpoint + */ + template + void RegisterCluster(const chip::ClusterId clusterId) + { + static_assert(std::is_base_of::value, "T must be derived from BaseCluster"); + auto cluster = std::make_shared(shared_from_this()); + mClusters[clusterId] = std::static_pointer_cast(cluster); + } + + /** + * @brief Returns a cluster of type T, if applicable. Returns nullptr otherwise + */ + template + memory::Strong GetCluster() + { + static_assert(std::is_base_of::value, "T must be derived from BaseCluster"); + for (const auto & pair : mClusters) + { + auto cluster = std::dynamic_pointer_cast(pair.second); + if (cluster) + { + return cluster; + } + } + return nullptr; + } +}; + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/core/Types.h b/examples/tv-casting-app/tv-casting-common/core/Types.h index 691fc3d2b36944..267b09642171b2 100644 --- a/examples/tv-casting-app/tv-casting-common/core/Types.h +++ b/examples/tv-casting-app/tv-casting-common/core/Types.h @@ -49,6 +49,8 @@ class AppParameters; class ByteSpanDataProvider; class ServerInitParamsProvider; +class EndpointListLoader; + } // namespace support }; // namespace casting diff --git a/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp b/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp index 1bfd75a93a47ca..b6ee4779d41d6e 100644 --- a/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp +++ b/examples/tv-casting-app/tv-casting-common/support/CastingStore.cpp @@ -125,6 +125,7 @@ std::vector CastingStore::ReadAll() ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); core::CastingPlayerAttributes attributes; + std::vector endpointAttributesList; while ((err = reader.Next()) == CHIP_NO_ERROR) { chip::TLV::Tag castingPlayerContainerTag = reader.GetTag(); @@ -196,13 +197,170 @@ std::vector CastingStore::ReadAll() continue; } + if (castingPlayerContainerTagNum == kCastingPlayerEndpointsContainerTag) + { + // Entering Endpoints container + chip::TLV::TLVType endpointsContainerType = chip::TLV::kTLVType_Array; + err = reader.EnterContainer(endpointsContainerType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + core::EndpointAttributes endpointAttributes; + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + // Entering Endpoint container + chip::TLV::TLVType endpointContainerType = chip::TLV::kTLVType_Structure; + err = reader.EnterContainer(endpointContainerType); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + chip::TLV::Tag endpointContainerTag = reader.GetTag(); + VerifyOrReturnValue(chip::TLV::IsContextTag(endpointContainerTag), std::vector(), + ChipLogError(AppServer, "Unexpected non-context TLV tag")); + + uint8_t endpointContainerTagNum = static_cast(chip::TLV::TagNumFromTag(endpointContainerTag)); + if (endpointContainerTagNum == kCastingPlayerEndpointIdTag) + { + err = reader.Get(endpointAttributes.mId); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (endpointContainerTagNum == kCastingPlayerEndpointVendorIdTag) + { + err = reader.Get(endpointAttributes.mVendorId); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (endpointContainerTagNum == kCastingPlayerEndpointProductIdTag) + { + err = reader.Get(endpointAttributes.mProductId); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + std::vector deviceTypeList; + if (endpointContainerTagNum == kCastingPlayerEndpointDeviceTypeListContainerTag) + { + // Entering DeviceTypeList container + chip::TLV::TLVType deviceTypeListContainerType = chip::TLV::kTLVType_Array; + err = reader.EnterContainer(deviceTypeListContainerType); + 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 DeviceTypeStruct container + chip::TLV::TLVType deviceTypeStructContainerType = chip::TLV::kTLVType_Structure; + err = reader.EnterContainer(deviceTypeStructContainerType); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.EnterContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + chip::app::Clusters::Descriptor::Structs::DeviceTypeStruct::DecodableType deviceTypeStruct; + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + chip::TLV::Tag deviceTypeStructContainerTag = reader.GetTag(); + VerifyOrReturnValue(chip::TLV::IsContextTag(deviceTypeStructContainerTag), + std::vector(), + ChipLogError(AppServer, "Unexpected non-context TLV tag")); + + uint8_t deviceTypeStructContainerTagNum = + static_cast(chip::TLV::TagNumFromTag(deviceTypeStructContainerTag)); + if (deviceTypeStructContainerTagNum == kCastingPlayerEndpointDeviceTypeTag) + { + err = reader.Get(deviceTypeStruct.deviceType); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + + if (deviceTypeStructContainerTagNum == kCastingPlayerEndpointDeviceTypeRevisionTag) + { + err = reader.Get(deviceTypeStruct.revision); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.Get failed %" CHIP_ERROR_FORMAT, err.Format())); + continue; + } + } + + if (err == CHIP_END_OF_TLV) + { + // Exiting DeviceTypeStruct container + err = reader.ExitContainer(deviceTypeStructContainerType); + VerifyOrReturnValue(err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, + "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, + err.Format())); + + deviceTypeList.push_back(deviceTypeStruct); + break; + } + } + if (err == CHIP_END_OF_TLV) + { + // Exiting DeviceTypeList container + err = reader.ExitContainer(deviceTypeListContainerType); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + endpointAttributes.mDeviceTypeList = deviceTypeList; + break; + } + continue; + } + } + + if (err == CHIP_END_OF_TLV) + { + // Exiting Endpoint container + err = reader.ExitContainer(endpointContainerType); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + + endpointAttributesList.push_back(endpointAttributes); + break; + } + } + + if (err == CHIP_END_OF_TLV) + { + // Exiting Endpoints container + err = reader.ExitContainer(endpointsContainerType); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, std::vector(), + ChipLogError(AppServer, "TLVReader.ExitContainer failed %" CHIP_ERROR_FORMAT, err.Format())); + break; + } + + 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())); + + // create a castingPlayer with Endpoints and add it to the castingPlayers to be returned core::CastingPlayer castingPlayer(attributes); + for (auto & endpointAttributes : endpointAttributesList) + { + std::shared_ptr endpoint(new core::Endpoint(&castingPlayer, endpointAttributes)); + castingPlayer.RegisterEndpoint(endpoint); + } castingPlayers.push_back(castingPlayer); break; } @@ -321,6 +479,65 @@ CHIP_ERROR CastingStore::WriteAll(std::vector castingPlayer ReturnErrorOnFailure(tlvWriter.PutBytes(chip::TLV::ContextTag(kCastingPlayerHostNameTag), (const uint8_t *) castingPlayer.GetHostName(), static_cast(strlen(castingPlayer.GetHostName()) + 1))); + + // Endpoints container starts + chip::TLV::TLVType endpointsContainerType = chip::TLV::kTLVType_Array; + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointsContainerTag), + chip::TLV::kTLVType_Array, endpointsContainerType)); + std::vector> endpoints = core::CastingPlayer::GetTargetCastingPlayer()->GetEndpoints(); + for (auto & endpoint : endpoints) + { + chip::TLV::TLVType endpointContainerType = chip::TLV::kTLVType_Structure; + // Endpoint container starts + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointContainerTag), + chip::TLV::kTLVType_Structure, endpointContainerType)); + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointIdTag), endpoint->GetId())); + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointVendorIdTag), endpoint->GetVendorId())); + ReturnErrorOnFailure( + tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointProductIdTag), endpoint->GetProductId())); + + // DeviceTypeList container starts + chip::TLV::TLVType deviceTypeListContainerType = chip::TLV::kTLVType_Array; + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointDeviceTypeListContainerTag), + chip::TLV::kTLVType_Array, deviceTypeListContainerType)); + std::vector deviceTypeList = + endpoint->GetDeviceTypeList(); + for (chip::app::Clusters::Descriptor::Structs::DeviceTypeStruct::DecodableType deviceTypeStruct : deviceTypeList) + { + chip::TLV::TLVType deviceTypeStructContainerType = chip::TLV::kTLVType_Structure; + // DeviceTypeStruct container starts + ReturnErrorOnFailure( + tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointDeviceTypeStructContainerTag), + chip::TLV::kTLVType_Structure, deviceTypeStructContainerType)); + ReturnErrorOnFailure( + tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointDeviceTypeTag), deviceTypeStruct.deviceType)); + ReturnErrorOnFailure( + tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointDeviceTypeRevisionTag), deviceTypeStruct.revision)); + + // DeviceTypeStruct container ends + ReturnErrorOnFailure(tlvWriter.EndContainer(deviceTypeStructContainerType)); + } + // DeviceTypeList container ends + ReturnErrorOnFailure(tlvWriter.EndContainer(deviceTypeListContainerType)); + + // ServerList container starts + chip::TLV::TLVType serverListContainerType = chip::TLV::kTLVType_Array; + ReturnErrorOnFailure(tlvWriter.StartContainer(chip::TLV::ContextTag(kCastingPlayerEndpointServerListContainerTag), + chip::TLV::kTLVType_Array, serverListContainerType)); + std::vector serverList = endpoint->GetServerList(); + for (chip::ClusterId clusterId : serverList) + { + ReturnErrorOnFailure(tlvWriter.Put(chip::TLV::ContextTag(kCastingPlayerEndpointServerClusterIdTag), clusterId)); + } + // ServerList container ends + ReturnErrorOnFailure(tlvWriter.EndContainer(serverListContainerType)); + + // Endpoint container ends + ReturnErrorOnFailure(tlvWriter.EndContainer(endpointContainerType)); + } + // Endpoints container ends + ReturnErrorOnFailure(tlvWriter.EndContainer(endpointsContainerType)); + // CastingPlayer container ends ReturnErrorOnFailure(tlvWriter.EndContainer(castingPlayerContainerType)); } diff --git a/examples/tv-casting-app/tv-casting-common/support/CastingStore.h b/examples/tv-casting-app/tv-casting-common/support/CastingStore.h index 3d8ec7f613511b..563410fb380e3a 100644 --- a/examples/tv-casting-app/tv-casting-common/support/CastingStore.h +++ b/examples/tv-casting-app/tv-casting-common/support/CastingStore.h @@ -72,6 +72,7 @@ class CastingStore : public chip::FabricTable::Delegate enum CastingStoreTLVTag { kCastingStoreDataVersionTag = 1, + kCastingPlayersContainerTag, kCastingPlayerContainerTag, kCastingPlayerIdTag, @@ -83,6 +84,20 @@ class CastingStore : public chip::FabricTable::Delegate kCastingPlayerDeviceNameTag, kCastingPlayerHostNameTag, + kCastingPlayerEndpointsContainerTag, + kCastingPlayerEndpointContainerTag, + kCastingPlayerEndpointIdTag, + kCastingPlayerEndpointVendorIdTag, + kCastingPlayerEndpointProductIdTag, + + kCastingPlayerEndpointDeviceTypeListContainerTag, + kCastingPlayerEndpointDeviceTypeStructContainerTag, + kCastingPlayerEndpointDeviceTypeTag, + kCastingPlayerEndpointDeviceTypeRevisionTag, + + kCastingPlayerEndpointServerListContainerTag, + kCastingPlayerEndpointServerClusterIdTag, + kContextTagMaxNum = UINT8_MAX }; diff --git a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp index 704e4ef7295874..eaaf8dc44cc834 100644 --- a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp +++ b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.cpp @@ -21,6 +21,7 @@ #include "core/CastingPlayer.h" #include "core/Types.h" #include "support/CastingStore.h" +#include "support/EndpointListLoader.h" #include "app/clusters/bindings/BindingManager.h" @@ -66,9 +67,11 @@ void ChipDeviceEventHandler::Handle(const chip::DeviceLayer::ChipDeviceEvent * e [](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()); + + // this async call will Load all the endpoints with their respective attributes into the TargetCastingPlayer + // persist the TargetCastingPlayer information into the CastingStore and call mOnCompleted() + EndpointListLoader::GetInstance()->Initialize(&exchangeMgr, &sessionHandle); + EndpointListLoader::GetInstance()->Load(); }, [](void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error) { ChipLogError(AppServer, "ChipDeviceEventHandler::Handle: Connection to CastingPlayer failed"); diff --git a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h index 69142603ce40f0..e6759856be8caf 100644 --- a/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h +++ b/examples/tv-casting-app/tv-casting-common/support/ChipDeviceEventHandler.h @@ -27,7 +27,7 @@ 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 + * ChipDeviceEventHandler helps the CastingPlayer class commission with and connect to a CastingPlayer */ class ChipDeviceEventHandler { diff --git a/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp new file mode 100644 index 00000000000000..396a3229c23e2a --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.cpp @@ -0,0 +1,259 @@ +/* + * + * 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 "EndpointListLoader.h" + +#include "clusters/ContentLauncherCluster.h" +#include "clusters/MediaPlaybackCluster.h" +#include "clusters/TargetNavigatorCluster.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; + +EndpointListLoader * EndpointListLoader::_endpointListLoader = nullptr; + +EndpointListLoader::EndpointListLoader() {} + +EndpointListLoader * EndpointListLoader::GetInstance() +{ + if (_endpointListLoader == nullptr) + { + _endpointListLoader = new EndpointListLoader(); + } + return _endpointListLoader; +} + +void EndpointListLoader::Initialize(chip::Messaging::ExchangeManager * exchangeMgr, const chip::SessionHandle * sessionHandle) +{ + mExchangeMgr = exchangeMgr; + mSessionHandle = sessionHandle; + + for (const auto & binding : chip::BindingTable::GetInstance()) + { + if (binding.type == EMBER_UNICAST_BINDING && CastingPlayer::GetTargetCastingPlayer()->GetNodeId() == binding.nodeId) + { + // check to see if we discovered a new endpoint in the bindings + chip::EndpointId endpointId = binding.remote; + std::vector> endpoints = CastingPlayer::GetTargetCastingPlayer()->GetEndpoints(); + if (std::find_if(endpoints.begin(), endpoints.end(), [&endpointId](const memory::Strong & endpoint) { + return endpoint->GetId() == endpointId; + }) == endpoints.end()) + { + mNewEndpointsToLoad++; + } + } + } + + mPendingAttributeReads = mNewEndpointsToLoad * kTotalDesiredAttributes; + mEndpointAttributesList = new EndpointAttributes[mNewEndpointsToLoad]; + mEndpointServerLists = new std::vector[mNewEndpointsToLoad]; +} + +CHIP_ERROR EndpointListLoader::Load() +{ + ChipLogProgress(AppServer, "EndpointListLoader::Load() called"); + + VerifyOrReturnError(CastingPlayer::GetTargetCastingPlayer() != nullptr, CHIP_ERROR_INCORRECT_STATE); + + int endpointIndex = -1; + 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))); + if (binding.type == EMBER_UNICAST_BINDING && CastingPlayer::GetTargetCastingPlayer()->GetNodeId() == binding.nodeId) + { + // if we discovered a new Endpoint from the bindings, read its EndpointAttributes + chip::EndpointId endpointId = binding.remote; + std::vector> endpoints = CastingPlayer::GetTargetCastingPlayer()->GetEndpoints(); + if (std::find_if(endpoints.begin(), endpoints.end(), [&endpointId](const memory::Strong & endpoint) { + return endpoint->GetId() == endpointId; + }) == endpoints.end()) + { + // Read attributes and mEndpointAttributesList for (endpointIndex + 1) + ChipLogProgress(AppServer, "EndpointListLoader::Load Reading attributes for endpointId %d", endpointId); + mEndpointAttributesList[++endpointIndex].mId = endpointId; + ReadVendorId(&mEndpointAttributesList[endpointIndex]); + ReadProductId(&mEndpointAttributesList[endpointIndex]); + ReadDeviceTypeList(&mEndpointAttributesList[endpointIndex]); + ReadServerList(&mEndpointServerLists[endpointIndex], endpointId); + } + } + } + + return CHIP_NO_ERROR; +} + +void EndpointListLoader::Complete() +{ + ChipLogProgress(AppServer, "EndpointListLoader::Complete called with mPendingAttributeReads %lu", mPendingAttributeReads); + mPendingAttributeReads--; + if (mPendingAttributeReads == 0) + { + ChipLogProgress(AppServer, "EndpointListLoader::Complete ready to complete Loading endpoints"); + for (unsigned long i = 0; i < mNewEndpointsToLoad; i++) + { + EndpointAttributes endpointAttributes = mEndpointAttributesList[i]; + std::shared_ptr endpoint = + std::make_shared(CastingPlayer::GetTargetCastingPlayer(), endpointAttributes); + for (chip::ClusterId clusterId : mEndpointServerLists[i]) + { + switch (clusterId) + { + case chip::app::Clusters::ContentLauncher::Id: + endpoint->RegisterCluster(clusterId); + break; + + case chip::app::Clusters::MediaPlayback::Id: + endpoint->RegisterCluster(clusterId); + break; + + case chip::app::Clusters::TargetNavigator::Id: + endpoint->RegisterCluster(clusterId); + break; + + default: + ChipLogProgress(AppServer, "Skipping registration of clusterId %d for endpointId %d", clusterId, + endpointAttributes.mId); + break; + } + } + CastingPlayer::GetTargetCastingPlayer()->RegisterEndpoint(endpoint); + } + + ChipLogProgress(AppServer, "EndpointListLoader::Complete finished Loading endpoints"); + + // TODO cleanup + // delete mEndpointAttributesList; + mEndpointAttributesList = nullptr; + // delete mEndpointServerLists; + mEndpointServerLists = nullptr; + mExchangeMgr = nullptr; + mSessionHandle = nullptr; + mNewEndpointsToLoad = 0; + + // done loading endpoints, callback client OnCompleted + support::CastingStore::GetInstance()->AddOrUpdate(*CastingPlayer::GetTargetCastingPlayer()); + VerifyOrReturn(CastingPlayer::GetTargetCastingPlayer()->mOnCompleted); + CastingPlayer::GetTargetCastingPlayer()->mOnCompleted(CHIP_NO_ERROR, CastingPlayer::GetTargetCastingPlayer()); + } +} + +CHIP_ERROR EndpointListLoader::ReadVendorId(EndpointAttributes * endpointAttributes) +{ + MediaClusterBase cluster(*mExchangeMgr, *mSessionHandle, endpointAttributes->mId); + + return cluster.template ReadAttribute( + endpointAttributes, + [](void * context, + chip::app::Clusters::ApplicationBasic::Attributes::VendorID::TypeInfo::DecodableArgType decodableVendorId) { + EndpointAttributes * _endpointAttributes = static_cast(context); + _endpointAttributes->mVendorId = decodableVendorId; + EndpointListLoader::GetInstance()->Complete(); + }, + [](void * context, CHIP_ERROR err) { + EndpointAttributes * _endpointAttributes = static_cast(context); + ChipLogError(AppServer, "EndpointListLoader ReadAttribute(VendorID) failed for endpointID %d. Err: %" CHIP_ERROR_FORMAT, + _endpointAttributes->mId, err.Format()); + EndpointListLoader::GetInstance()->Complete(); + }); +} + +CHIP_ERROR EndpointListLoader::ReadProductId(EndpointAttributes * endpointAttributes) +{ + MediaClusterBase cluster(*mExchangeMgr, *mSessionHandle, endpointAttributes->mId); + + return cluster.template ReadAttribute( + endpointAttributes, + [](void * context, + chip::app::Clusters::ApplicationBasic::Attributes::ProductID::TypeInfo::DecodableArgType decodableProductId) { + EndpointAttributes * _endpointAttributes = static_cast(context); + _endpointAttributes->mProductId = decodableProductId; + EndpointListLoader::GetInstance()->Complete(); + }, + [](void * context, CHIP_ERROR err) { + EndpointAttributes * _endpointAttributes = static_cast(context); + ChipLogError(AppServer, + "EndpointListLoader ReadAttribute(ProductID) failed for endpointID %d. Err: %" CHIP_ERROR_FORMAT, + _endpointAttributes->mId, err.Format()); + EndpointListLoader::GetInstance()->Complete(); + }); +} + +CHIP_ERROR EndpointListLoader::ReadDeviceTypeList(EndpointAttributes * endpointAttributes) +{ + MediaClusterBase cluster(*mExchangeMgr, *mSessionHandle, endpointAttributes->mId); + + return cluster.template ReadAttribute( + endpointAttributes, + [](void * context, + chip::app::Clusters::Descriptor::Attributes::DeviceTypeList::TypeInfo::DecodableArgType decodableDeviceTypeList) { + EndpointAttributes * _endpointAttributes = static_cast(context); + auto iter = decodableDeviceTypeList.begin(); + while (iter.Next()) + { + auto & deviceType = iter.GetValue(); + _endpointAttributes->mDeviceTypeList.push_back(deviceType); + } + EndpointListLoader::GetInstance()->Complete(); + }, + [](void * context, CHIP_ERROR err) { + EndpointAttributes * _endpointAttributes = static_cast(context); + ChipLogError(AppServer, + "EndpointListLoader ReadAttribute(DeviceTypeList) failed for endpointID %d. Err: %" CHIP_ERROR_FORMAT, + _endpointAttributes->mId, err.Format()); + EndpointListLoader::GetInstance()->Complete(); + }); +} + +CHIP_ERROR EndpointListLoader::ReadServerList(std::vector * endpointServerList, chip::EndpointId endpointId) +{ + MediaClusterBase cluster(*mExchangeMgr, *mSessionHandle, endpointId); + + return cluster.template ReadAttribute( + endpointServerList, + [](void * context, + chip::app::Clusters::Descriptor::Attributes::ServerList::TypeInfo::DecodableArgType decodableServerList) { + std::vector * _endpointServerList = static_cast *>(context); + auto iter = decodableServerList.begin(); + while (iter.Next()) + { + auto & clusterId = iter.GetValue(); + _endpointServerList->push_back(clusterId); + } + EndpointListLoader::GetInstance()->Complete(); + }, + [](void * context, CHIP_ERROR err) { + ChipLogError(AppServer, "EndpointListLoader ReadAttribute(ServerList) failed. Err: %" CHIP_ERROR_FORMAT, err.Format()); + EndpointListLoader::GetInstance()->Complete(); + }); +} + +}; // namespace support +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.h b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.h new file mode 100644 index 00000000000000..4905e281e94fd6 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/support/EndpointListLoader.h @@ -0,0 +1,119 @@ +/* + * + * 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/Endpoint.h" +#include "core/Types.h" + +#include +#include +#include + +namespace matter { +namespace casting { +namespace support { + +/** + * @brief EndpointListLoader builds Endpoints corresponding to the CastingPlayer::GetTargetCastingPlayer by reading Bindings and + * fetching Endpoint attributes (like VendorID, ProductID, DeviceTypeList, ServerList, etc). It then loads all of these Endpoints + * into the CastingPlayer::GetTargetCastingPlayer. Finally, it calls mOnCompleted() on the CastingPlayer::GetTargetCastingPlayer to + * report the status of the Connection process to the client. + */ +class EndpointListLoader +{ +public: + static EndpointListLoader * GetInstance(); + + /** + * @brief Initializes EndpointListLoader with session info of the session the CastingApp just connected on. + */ + void Initialize(chip::Messaging::ExchangeManager * exchangeMgr, const chip::SessionHandle * sessionHandle); + + /** + * @brief Reads Bindings and fetches attributes (like VendorID, ProductID, DeviceTypeList, ServerList, etc) for each Endpoint + * discovered in them. + */ + CHIP_ERROR Load(); + +private: + EndpointListLoader(); + static EndpointListLoader * _endpointListLoader; + + /** + * @brief When all DesiredAttributes reads are completed, this function creates the list of Endpoints and loads/registers them + * on the CastingPlayer::GetTargetCastingPlayer + */ + void Complete(); + + /** + * @brief Fetches the VendorId for the endpoint with passed in endpointAttributes->mID + */ + CHIP_ERROR ReadVendorId(core::EndpointAttributes * endpointAttributes); + + /** + * @brief Fetches the ProductId for the endpoint with passed in endpointAttributes->mID + */ + CHIP_ERROR ReadProductId(core::EndpointAttributes * endpointAttributes); + + /** + * @brief Fetches the DeviceTypeList for the endpoint with passed in endpointAttributes->mID + */ + CHIP_ERROR ReadDeviceTypeList(core::EndpointAttributes * endpointAttributes); + + /** + * @brief Fetches the ServerList for the endpoint with passed in endpointAttributes->mID + */ + CHIP_ERROR ReadServerList(std::vector * endpointServerList, chip::EndpointId endpointId); + + chip::Messaging::ExchangeManager * mExchangeMgr = nullptr; + const chip::SessionHandle * mSessionHandle = nullptr; + unsigned long mNewEndpointsToLoad = 0; + unsigned long mPendingAttributeReads = 0; + core::EndpointAttributes * mEndpointAttributesList = nullptr; + std::vector * mEndpointServerLists = nullptr; +}; + +/** + * @brief Enumerated list of all Endpoint Attributes that need to be fetched + */ +enum DesiredAttributes +{ + kVendorId = 0, + kProductId, + kDeviceTypeList, + kServerList, + + kTotalDesiredAttributes +}; + +/** + * @brief MediaClusterBase is used by the EndpointListLoader to invoke controller/CHIPCluster.h#ReadAttribute() API calls + */ +class MediaClusterBase : public chip::Controller::ClusterBase +{ +public: + MediaClusterBase(chip::Messaging::ExchangeManager & exchangeManager, const chip::SessionHandle & session, + chip::EndpointId endpoint) : + ClusterBase(exchangeManager, session, endpoint) + {} +}; + +}; // namespace support +}; // namespace casting +}; // namespace matter