diff --git a/.github/.wordlist.txt b/.github/.wordlist.txt index eb27e5855e1bc5..24cdeaa092f691 100644 --- a/.github/.wordlist.txt +++ b/.github/.wordlist.txt @@ -74,7 +74,10 @@ ApplyUpdate applyUpdateAction ApplyUpdateRequest ApplyUpdateResponse +appParameters +AppParameters approver +AppServer appspot appwrite aps @@ -191,6 +194,11 @@ capacitive CarbonDioxideConcentrationMeasurement CarbonMonoxideConcentrationMeasurement CaseAdminNode +castingApp +CastingApp +castingAppDidReceiveRequestForDeviceAttestationCredentials +CastingClient +CastingPlayer CatalogVendorId CBB cbd @@ -204,6 +212,7 @@ CCXML CDEEDC CDVersionNumber ced +certificationDeclaration cfg CFLAGS cgit @@ -227,6 +236,8 @@ ChipEchoResponder ChipImInitiator ChipImResponder ChipLight +ChipLogError +ChipLogProgress ChipMessageLayer CHIPOBLE CHIPProjectAppConfig @@ -277,7 +288,9 @@ Commissionable CommissionableDataProvider commissionables commissionee +commissionersCount CommissioningFlow +CommonCaseDeviceServerInitParamsProvider commondatastorage CONF CONFIG @@ -321,6 +334,7 @@ crypto cryptographic CSA csg +csrData csrrequest csu csv @@ -333,6 +347,7 @@ CurrentSaturation customAcl customizations cvfJ +CXE cxx CY CYW @@ -344,6 +359,7 @@ DAPLINK DataFrame datamodel DataModelRevision +DataProvider dataset datasets DataVersion @@ -369,6 +385,7 @@ DefaultOTARequestor DefaultOTARequestorDriver DefaultOTARequestorStorage DefaultSuccess +defaultTestPasscode definedValue DehumidificationControl DelayedActionTime @@ -388,6 +405,7 @@ dev devcontainer devCtrl DevelopmentCerts +deviceAttestationCert DeviceAttestationCredentialsProvider DeviceAttestationCredsExample DeviceCaCerts @@ -418,6 +436,7 @@ DHCPv dhrishi DiagnosticLogs dialout +didReceiveRequestToSignCertificateRequest diffstat diffsyms dimmable @@ -426,6 +445,8 @@ dirs disableNotifyUpdateApplied disambiguated discoverable +DiscoveryDelegate +DiscoveryDelegateImpl DispatchEvent DispatchEventToApplication DissolvedOxygenConcentrationMeasurement @@ -551,6 +572,7 @@ fffff Fi filepath fini +firmwareInformation FixedLabel flashdebug FlowMeasurement @@ -566,10 +588,12 @@ fstab fsync ftd fullclean +func fuzzer FW gbl gcloud +gCommissionableDataProvider GDB gdbgui gdbserver @@ -583,12 +607,20 @@ GenericPlatformManagerImpl GenericThreadConfigurationManagerImpl GenericThreadStackManagerImpl GenericWiFiConfigurationManagerImpl +GetDefaultDACVerifier GetDeviceId GetDeviceInfo GetDns +GetExampleDACProvider +GetId +GetInstance GetIP +GetLongValue getManualTests +GetRandU +GetSetupPasscode getstarted +GetTestAttestationTrustStore getTests GH ghcr @@ -623,6 +655,8 @@ Gv gz gzbf HaloaceticAcidsConcentrationMeasurement +HandleOnAdded +HandleOnUpdated hardcoded hardknott hardwarever @@ -686,6 +720,7 @@ Infineon ini init InitArgs +InitCommissionableDataProvider inlined InputLoop installDebug @@ -733,10 +768,14 @@ kAdminister kbd kBusy kCase +kCertificationDeclaration Kconfig kDacPublicKey +kDevelopmentDAC KeypadInput keyset +KeyValueStoreMgr +KeyValueStoreMgrImpl kGroup kInvalidCommandId KitProg @@ -745,8 +784,17 @@ kNewButton kNodeIdNotSpecified knownissues kOperate +KPAI kPAKEParameterError kPase +kQ +kRotatingDeviceIDUniqueIDLength +kSecAttrKeyClass +kSecAttrKeyClassPrivate +kSecAttrKeySizeInBits +kSecAttrKeyType +kSecAttrKeyTypeECSECPrimeRandom +kSpake kView KVS kWindowNotOpen @@ -789,12 +837,15 @@ LightingApp LightingColor LightingState LinkSoftwareAndDocumentationPack +LinuxCommissionableDataProvider +LinuxDeviceOptions LocalConfigDisabled localedef localhost LocalizationConfiguration localstatedir LockingState +LogDetail loopback LowPower LPC @@ -867,6 +918,8 @@ microcontroller microcontrollers MicroSD middleware +MII +MIIB minApplicableSoftwareVersion Minicom MinInterval @@ -890,12 +943,15 @@ MoveWithOnOff MPSL MRP MTD +MTRDeviceAttestationCredentials MTU Multiband Multicast multilib Multiprotocol multithreaded +MutableByteSpan +MutableByteSpanDataProvider mutexes mv MX @@ -939,6 +995,8 @@ nrfconnect nrfdks nrfutil nrfxlib +NSData +NSDictionary NTAG nullable nullptr @@ -949,6 +1007,7 @@ nwdiag nwk NXP objcopy +objectivec OccupancySensing OctetString OECORE @@ -1021,6 +1080,7 @@ params PartNumber PASE Passcode +passRetained PBKDF pbuf pbufs @@ -1037,12 +1097,15 @@ pem percentageLiftValue perfetto periodicQueryTimeout +PersistedStorage pexpect pickString PID Pigweed PinCode pinrequest +pIterationCount +pIterations pkgconfig PKI plaintext @@ -1064,7 +1127,11 @@ pre preprocessor Presetup PressureMeasurement +privateKey +PrivateKey +privateKeyRef prj +productAttestationIntermediateCert ProductID ProductLabel ProductName @@ -1082,14 +1149,17 @@ ProxyValid ProxyView PRs PSA +pSalt PSCAN PSECT PSK PSoC PTR pts +PublicKey PulseWidthModulation PumpConfigurationAndControl +pVerifier pwd PWM PXXXF @@ -1110,6 +1180,7 @@ QPG QRCode qrcodetool QRCodeUrl +qrYA QSPI QueryImage QueryImageResponse @@ -1126,6 +1197,7 @@ ReadConfigValue readelf readfds README +READMEs readonly readthedocs Reag @@ -1162,6 +1234,10 @@ rmw rodata Rollershade rootfs +rotatingDeviceIdUniqueId +RotatingDeviceIdUniqueId +RotatingDeviceIdUniqueIdProvider +rotatingDeviceIdUniqueIdSpan RPC RPCs RPi @@ -1204,6 +1280,8 @@ sdkconfig SDKs SDKTARGETSYSROOT sdl +SecKey +SecKeyCreateWithData SecureCertDACProvider SED SEGGER @@ -1214,6 +1292,7 @@ sendto seqdiag SERIALDEVICE SerialNumber +serverInitParamsProvider ServiceId SetDns SetImageProcessorDelegate @@ -1223,8 +1302,10 @@ setpin setpoint SetpointRaiseLower SetRequestorInstance +setupPasscode SetUpPINCode SetupQRCode +SetValue sexualized sfv SHA @@ -1241,6 +1322,7 @@ SimpleFileExFlags SimpleLink SiWx sizedb +sizeof sl SLAAC SLTB @@ -1277,9 +1359,9 @@ StatusCode stderr stdout sterm -stmicroelectronics -stm stlink +stm +stmicroelectronics storagepath str strcpy @@ -1337,6 +1419,7 @@ TestGenExample TestGroupDemoConfig TestMultiRead TestName +TestOnlyCommissionableDataProvider TESTPASSWD TestPICS TESTSSID @@ -1352,11 +1435,11 @@ textboxes TFT ThermostatUserInterfaceConfiguration ThIsIsNoTMyReAlGiThUbToKeNSoDoNoTtRy +thread ThreadNetworkDiagnostics threadOperationalDataset ThreadStackManager ThreadStackManagerImpl -thread ths Thunderboard timedInteractionTimeoutMs @@ -1427,6 +1510,7 @@ unfocus Unicast UniFlash UnitLocalization +Unmanaged unpair unprovisioned Unsecure @@ -1465,6 +1549,8 @@ venv ver Verifier Verifiers +VerifyOrDie +VerifyOrReturnValue VID vids virtualenv diff --git a/docs/examples/index.md b/docs/examples/index.md index b28c7c6e2ebd7f..83a1a6f936c90c 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -273,6 +273,7 @@ tv-app/**/README :maxdepth: 1 tv-casting-app/**/README +tv-casting-app/APIs.md ``` ## Window example diff --git a/examples/tv-casting-app/APIs.md b/examples/tv-casting-app/APIs.md new file mode 100644 index 00000000000000..2e9ecc191eb0a8 --- /dev/null +++ b/examples/tv-casting-app/APIs.md @@ -0,0 +1,470 @@ +# Matter Casting APIs + +Matter Casting consists of three parts: + +- **The mobile app**: For most content providers, this would be your + consumer-facing mobile app. By making your mobile app a Matter "Casting + Client", you enable the user to discover casting targets, cast content, and + control casting sessions. The + [example Matter tv-casting-app](https://github.com/project-chip/connectedhomeip/tree/master/examples/tv-casting-app) + for Android / iOS and Linux builds on top of the Matter SDK to demonstrate + how a TV Casting mobile app works. +- **The TV content app**: For most content providers, this would be your + consumer-facing app on a Smart TV. By enhancing your TV app to act as a + Matter "Content app", you enable Matter Casting Clients to cast content. The + [example Matter content-app](https://github.com/project-chip/connectedhomeip/tree/master/examples/tv-app/android/App/content-app) + for Android builds on top of the Matter SDK to demonstrate how a TV Content + app works. +- **The TV platform app**: The TV platform app implements the Casting Video + Player device type and provides common capabilities around media playback on + the TV such as play/pause, keypad navigation, input and output control, + content search, and an implementation of an app platform as described in the + media chapter of the device library specification. This is generally + implemented by the TV manufacturer. The + [example Matter tv-app](https://github.com/project-chip/connectedhomeip/tree/master/examples/tv-app) + for Android builds on top of the Matter SDK to demonstrate how a TV platform + app works. + +This document describes how enable your Android and iOS apps to act as a Matter +"Casting Client". This documentation is also designed to work with the example +[example Matter tv-casting-app](https://github.com/project-chip/connectedhomeip/tree/master/examples/tv-casting-app) +samples so you can see the experience end to end. + +## Introduction + +A Casting Client (e.g. a mobile phone app) is expected to be a Matter +Commissionable Node and a `CastingPlayer` (i.e. a TV) is expected to be a Matter +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 +[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`. + +The steps to start a casting session are: + +1. [Initialize](#initialize-the-casting-client) the `CastingClient` using the + Matter SDK. +1. [Discover](#discover-casting-players) `CastingPlayer` devices using Matter + Commissioner discovery. +1. [Connect](#connect-to-a-casting-player) to the `CastingPlayer` to discover + available endpoints. By connecting, the 'CastingClient' will send a User + Directed Commissioning (UDC) request to the 'CastingPlayer' device in order + to make a Matter commissioning request. The 'CastingPlayer' will then obtain + the appropriate user consent to allow a connection from this 'CastingClient' + and obtain the setup code needed to commission the 'CastingClient'. The setup + code will typically come from a corresponding TV content app or be input by + the user. +1. [Select](#select-an-endpoint-on-the-casting-player) an available `Endpoint` + hosted by the `CastingPlayer`. + +Next, you're ready to: + +1. [Issue commands](#issuing-commands) to the `Endpoint`. +1. [Read](#read-operations) endpoint attributes like playback state. +1. [Subscribe](#subscriptions) to playback events. + +## Build and Setup + +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. + +### Initialize the Casting Client + +_{Complete Initialization examples: [Linux](linux/simple-app.cpp) | +[Android](android/App/app/src/main/java/com/matter/casting/InitializationExample.java) +| [iOS](darwin/TvCasting/TvCasting/MTRInitializationExample.swift)}_ + +A Casting Client must first initialize the Matter SDK and define the following +"DataProvider" objects for the the Matter Casting library to use throughout the +client's lifecycle: + +1. **Rotating Device Identifier** - Refer to the Matter specification for + details on how to generate the + [Rotating Device Identifier](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/rendezvous/DeviceDiscovery.adoc#245-rotating-device-identifier)). + Then, instantiate a `DataProvider` object as described below. + + On Linux, define a `RotatingDeviceIdUniqueIdProvider` to provide the Casting + Client's RotatingDeviceIdUniqueId, by implementing a + `matter:casting::support::MutableByteSpanDataProvider`: + + ```c++ + class RotatingDeviceIdUniqueIdProvider : public MutableByteSpanDataProvider { + private: + chip::MutableByteSpan rotatingDeviceIdUniqueIdSpan; + uint8_t rotatingDeviceIdUniqueId[chip::DeviceLayer::ConfigurationManager::kRotatingDeviceIDUniqueIDLength]; + + public: + RotatingDeviceIdUniqueIdProvider() { + // generate a random Unique ID for this example app for demonstration + for (size_t i = 0; i < sizeof(rotatingDeviceIdUniqueId); i++) { + rotatingDeviceIdUniqueId[i] = chip::Crypto::GetRandU8(); + } + rotatingDeviceIdUniqueIdSpan = chip::MutableByteSpan(rotatingDeviceIdUniqueId); + } + + chip::MutableByteSpan * Get() { return &rotatingDeviceIdUniqueIdSpan; } + }; + ``` + + On Android, define a `rotatingDeviceIdUniqueIdProvider` to provide the + Casting Client's RotatingDeviceIdUniqueId, by implementing a + `com.matter.casting.support.DataSource`: + + ```java + private static final DataProvider rotatingDeviceIdUniqueIdProvider = + new DataProvider() { + private static final String ROTATING_DEVICE_ID_UNIQUE_ID = + "EXAMPLE_IDENTIFIER"; // dummy value for demonstration only + + @Override + public byte[] get() { + return ROTATING_DEVICE_ID_UNIQUE_ID.getBytes(); + } + }; + ``` + + On iOS, define the + `func castingAppDidReceiveRequestForRotatingDeviceIdUniqueId` in a class, + `MTRAppParametersDataSource`, that implements the `MTRDataSource`: + + ```objectivec + class MTRAppParametersDataSource : NSObject, MTRDataSource + { + func castingAppDidReceiveRequestForRotatingDeviceIdUniqueId(_ sender: Any) -> Data { + // dummy value, with at least 16 bytes (ConfigurationManager::kMinRotatingDeviceIDUniqueIDLength), for demonstration only + return "0123456789ABCDEF".data(using: .utf8)! + } + ... + } + ``` + +2. **Commissioning Data** - This object contains the passcode, discriminator, + etc which identify the app and are provided to the CastingPlayer during the + commissioning process. Refer to the Matter specification's + [Onboarding Payload](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/qr_code/OnboardingPayload.adoc#ref_OnboardingPayload) + section for details on commissioning data. + + On Linux, define a function `InitCommissionableDataProvider` to initialize + initialize a `LinuxCommissionableDataProvider` that can provide the required + values to the CastingApp. + + ```c++ + CHIP_ERROR InitCommissionableDataProvider(LinuxCommissionableDataProvider & provider, LinuxDeviceOptions & options) { + chip::Optional setupPasscode; + + if (options.payload.setUpPINCode != 0) { + setupPasscode.SetValue(options.payload.setUpPINCode); + } else if (!options.spake2pVerifier.HasValue()) { + // default to TestOnlyCommissionableDataProvider for demonstration + uint32_t defaultTestPasscode = 0; + chip::DeviceLayer::TestOnlyCommissionableDataProvider TestOnlyCommissionableDataProvider; + VerifyOrDie(TestOnlyCommissionableDataProvider.GetSetupPasscode(defaultTestPasscode) == CHIP_NO_ERROR); + setupPasscode.SetValue(defaultTestPasscode); + options.payload.setUpPINCode = defaultTestPasscode; + } + uint32_t spake2pIterationCount = chip::Crypto::kSpake2p_Min_PBKDF_Iterations; + if (options.spake2pIterations != 0) { + spake2pIterationCount = options.spake2pIterations; + } + return provider.Init(options.spake2pVerifier, options.spake2pSalt, spake2pIterationCount, setupPasscode, + options.payload.discriminator.GetLongValue()); + } + ``` + + On Android, define a `commissioningDataProvider` that can provide the + required values to the CastingApp. + + ```java + private static final DataProvider commissionableDataProvider = + new DataProvider() { + @Override + public CommissionableData get() { + // dummy values for demonstration only + return new CommissionableData(20202021, 3874); + } + }; + ``` + + On iOS, add a `func commissioningDataProvider` to the + `MTRAppParametersDataSource` class defined above, that can provide the + required values to the `MTRCastingApp`. + + ```objectivec + func castingAppDidReceiveRequestForCommissionableData(_ sender: Any) -> MTRCommissionableData { + // dummy values for demonstration only + return MTRCommissionableData( + passcode: 20202021, + discriminator: 3874, + spake2pIterationCount: 1000, + spake2pVerifier: nil, + spake2pSalt: nil) + } + ``` + +3. **Device Attestation Credentials** - This object contains the + `DeviceAttestationCertificate`, `ProductAttestationIntermediateCertificate`, + etc. and implements a way to sign messages when called upon by the Matter TV + Casting Library as part of the + [Device Attestation process](https://github.com/CHIP-Specifications/connectedhomeip-spec/blob/master/src/device_attestation/Device_Attestation_Specification.adoc) + during commissioning. + + On Linux, implement a define a `dacProvider` to provide the Casting Client's + Device Attestation Credentials, by implementing a + `chip::Credentials::DeviceAttestationCredentialsProvider`. For this example, + we will use the `chip::Credentials::Examples::ExampleDACProvider` + + On Android, define a `dacProvider` to provide the Casting Client's Device + Attestation Credentials, by implementing a + `com.matter.casting.support.DACProvider`: + + ```java + private static final DACProvider dacProvider = new DACProviderStub(); + private final static DataProvider dacProvider = new DataProvider() { + private static final String kDevelopmentDAC_Cert_FFF1_8001 = "MIIB5z......CXE1M="; // dummy values for demonstration only + private static final String kDevelopmentDAC_PrivateKey_FFF1_8001 = "qrYAror......StE+/8="; + private static final String KPAI_FFF1_8000_Cert_Array = "MIIByzC......pwP4kQ=="; + + @Override + public DeviceAttestationCredentials get() { + DeviceAttestationCredentials deviceAttestationCredentials = new DeviceAttestationCredentials() { + @Override + public byte[] SignWithDeviceAttestationKey(byte[] message) { + try { + byte[] privateKeyBytes = Base64.decode(kDevelopmentDAC_PrivateKey_FFF1_8001, Base64.DEFAULT); + AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("EC"); + algorithmParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algorithmParameters.getParameterSpec(ECParameterSpec.class); + ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(1, privateKeyBytes), parameterSpec); + + KeyFactory keyFactory = KeyFactory.getInstance("EC"); + PrivateKey privateKey = keyFactory.generatePrivate(ecPrivateKeySpec); + + Signature signature = Signature.getInstance("SHA256withECDSA"); + signature.initSign(privateKey); + signature.update(message); + + return signature.sign(); + } catch (Exception e) { + return null; + } + } + }; + + deviceAttestationCredentials.setDeviceAttestationCert( + Base64.decode(kDevelopmentDAC_Cert_FFF1_8001, Base64.DEFAULT)); + deviceAttestationCredentials.setProductAttestationIntermediateCert( + Base64.decode(KPAI_FFF1_8000_Cert_Array, Base64.DEFAULT)); + return deviceAttestationCredentials; + } + }; + ``` + + On iOS, add functions + `castingAppDidReceiveRequestForDeviceAttestationCredentials` and + `didReceiveRequestToSignCertificateRequest` to the + `MTRAppParametersDataSource` class defined above, that can return + MTRDeviceAttestationCredentials and sign messages for the Casting Client, + respectively. + + ```objectivec + // dummy DAC values for demonstration only + let kDevelopmentDAC_Cert_FFF1_8001: Data = Data(base64Encoded: "MIIB....CXE1M=")!; + let kDevelopmentDAC_PrivateKey_FFF1_8001: Data = Data(base64Encoded: "qrYAtE+/8=")!; + let kDevelopmentDAC_PublicKey_FFF1_8001: Data = Data(base64Encoded: "BEY6I=")!; + let KPAI_FFF1_8000_Cert_Array: Data = Data(base64Encoded: "MIIB4kQ==")!; + let kCertificationDeclaration: Data = Data(base64Encoded: "MIIfA==")!; + + func castingAppDidReceiveRequestForDeviceAttestationCredentials(_ sender: Any) -> MTRDeviceAttestationCredentials { + return MTRDeviceAttestationCredentials( + certificationDeclaration: kCertificationDeclaration, + firmwareInformation: Data(), + deviceAttestationCert: kDevelopmentDAC_Cert_FFF1_8001, + productAttestationIntermediateCert: KPAI_FFF1_8000_Cert_Array) + } + + func castingApp(_ sender: Any, didReceiveRequestToSignCertificateRequest csrData: Data) -> Data { + var privateKey = Data() + privateKey.append(kDevelopmentDAC_PublicKey_FFF1_8001); + privateKey.append(kDevelopmentDAC_PrivateKey_FFF1_8001); + + let privateKeyRef: SecKey = SecKeyCreateWithData(privateKey as NSData, + [ + kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, + kSecAttrKeyClass: kSecAttrKeyClassPrivate, + kSecAttrKeySizeInBits: 256 + ] as NSDictionary, nil)! + + let _:Unmanaged = Unmanaged.passRetained(privateKeyRef); + + // use SecKey above to sign csrData and return resulting value + } + ``` + +Once you have created the `DataProvider` objects above, you are ready to +initialize the Casting App as described below. Note: When you initialize the +Casting client, make sure your code initializes it only once, before it starts a +Matter casting session. + +On Linux, create an AppParameters object using the +`RotatingDeviceIdUniqueIdProvider`, `LinuxCommissionableDataProvider`, +`CommonCaseDeviceServerInitParamsProvider`, `ExampleDACProvider` and +`DefaultDACVerifier`, and call `CastingApp::GetInstance()->Initialize` with it. + +```c++ +LinuxCommissionableDataProvider gCommissionableDataProvider; +int main(int argc, char * argv[]) { + // Create AppParameters that need to be passed to CastingApp.Initialize() + AppParameters appParameters; + RotatingDeviceIdUniqueIdProvider rotatingDeviceIdUniqueIdProvider; + CommonCaseDeviceServerInitParamsProvider serverInitParamsProvider; + CHIP_ERROR err = CHIP_NO_ERROR; + err = InitCommissionableDataProvider(gCommissionableDataProvider, LinuxDeviceOptions::GetInstance()); + VerifyOrReturnValue( + err == CHIP_NO_ERROR, 0, + ChipLogError(AppServer, "Initialization of CommissionableDataProvider failed %" CHIP_ERROR_FORMAT, err.Format())); + err = appParameters.Create(&rotatingDeviceIdUniqueIdProvider, &gCommissionableDataProvider, + chip::Credentials::Examples::GetExampleDACProvider(), + GetDefaultDACVerifier(chip::Credentials::GetTestAttestationTrustStore()), &serverInitParamsProvider); + VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, + ChipLogError(AppServer, "Creation of AppParameters failed %" CHIP_ERROR_FORMAT, err.Format())); + + // Initialize the CastingApp + err = CastingApp::GetInstance()->Initialize(appParameters); + VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, + ChipLogError(AppServer, "Initialization of CastingApp failed %" CHIP_ERROR_FORMAT, err.Format())); + + // Initialize Linux KeyValueStoreMgr + chip::DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl().Init(CHIP_CONFIG_KVS_PATH); + + // Start the CastingApp + err = CastingApp::GetInstance()->Start(); + VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, + ChipLogError(AppServer, "CastingApp::Start failed %" CHIP_ERROR_FORMAT, err.Format())); + ... +} +``` + +On Android, create an AppParameters object using the +`rotatingDeviceIdUniqueIdProvider`, `commissioningDataProvider`, `dacProvider` +and `DataProvider`, and call +`CastingApp.getInstance().initialize` with it. + +```java +public static MatterError initAndStart(Context applicationContext) { + // Create an AppParameters object to pass in global casting parameters to the SDK + final AppParameters appParameters = + new AppParameters( + applicationContext, + new DataProvider() { + @Override + public ConfigurationManager get() { + return new PreferencesConfigurationManager( + applicationContext, "chip.platform.ConfigurationManager"); + } + }, + rotatingDeviceIdUniqueIdProvider, + commissionableDataProvider, + dacProvider); + + // Initialize the SDK using the appParameters and check if it returns successfully + MatterError err = CastingApp.getInstance().initialize(appParameters); + if (err.hasError()) { + Log.e(TAG, "Failed to initialize Matter CastingApp"); + return err; + } + + // Start the CastingApp + err = CastingApp.getInstance().start(); + if (err.hasError()) { + Log.e(TAG, "Failed to start Matter CastingApp"); + return err; + } + return err; +} +``` + +On iOS, call `MTRCastingApp.initialize` with an object of the +`MTRAppParametersDataSource`. + +```objectivec +func initialize() -> MatterError { + if let castingApp = MTRCastingApp.getSharedInstance() { + return castingApp.initialize(with: MTRAppParametersDataSource()) + } else { + return MATTER_ERROR_INCORRECT_STATE + } +} +``` + +### Discover Casting Players + +_{Complete Discovery examples: [Linux](linux/simple-app.cpp)}_ + +The Casting Client discovers `CastingPlayers` using Matter Commissioner +discovery over DNS-SD by listening for CastingPlayer events as they are +discovered, updated, or lost from the network. + +On Linux, define a `DiscoveryDelegateImpl` that implements the +`matter::casting::core::DiscoveryDelegate`. + +```c++ +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()); + } +}; +``` + +Finally, register these listeners and start discovery. + +On Linux, register an instance of the `DiscoveryDelegateImpl` with +`matter::casting::core::CastingPlayerDiscovery` by calling SetDelegate on its +singleton instance. Then, call `StartDiscovery` by optionally specifying the +`kTargetPlayerDeviceType` to filter results by. + +```c++ +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())); +chip::DeviceLayer::PlatformMgr().RunEventLoop(); +... +``` + +### Connect to a Casting Player + +### Select an Endpoint on the Casting Player + +## Interacting with a Casting Endpoint + +### Issuing Commands + +### Read Operations + +### Subscriptions diff --git a/examples/tv-casting-app/android/App/.idea/gradle.xml b/examples/tv-casting-app/android/App/.idea/gradle.xml index a2d7c21338e98a..526b4c25c6813e 100644 --- a/examples/tv-casting-app/android/App/.idea/gradle.xml +++ b/examples/tv-casting-app/android/App/.idea/gradle.xml @@ -13,6 +13,7 @@ + diff --git a/examples/tv-casting-app/linux/simple-app.cpp b/examples/tv-casting-app/linux/simple-app.cpp index 26f9e2825ff7d9..cd2f71eb612159 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 "core/CastingPlayer.h" +#include "core/CastingPlayerDiscovery.h" #include "core/Types.h" #include @@ -76,6 +78,33 @@ 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. */ @@ -148,6 +177,17 @@ 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); + 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); + VerifyOrReturnValue(err == CHIP_NO_ERROR, 0, + ChipLogError(AppServer, "CastingPlayerDiscovery::StartDiscovery failed %" CHIP_ERROR_FORMAT, err.Format())); + chip::DeviceLayer::PlatformMgr().RunEventLoop(); return 0; diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index 115a0b7e4bbfb0..f555a62a181a41 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -89,6 +89,9 @@ chip_data_model("tv-casting-common") { sources += [ "core/CastingApp.cpp", "core/CastingApp.h", + "core/CastingPlayer.h", + "core/CastingPlayerDiscovery.cpp", + "core/CastingPlayerDiscovery.h", "core/Types.h", "support/AppParameters.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 f8d66b03e0ac0c..bc495a27fff870 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp +++ b/examples/tv-casting-app/tv-casting-common/core/CastingApp.cpp @@ -44,7 +44,7 @@ CastingApp * CastingApp::GetInstance() CHIP_ERROR CastingApp::Initialize(const AppParameters & appParameters) { - VerifyOrReturnError(mState == UNINITIALIZED, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mState == CASTING_APP_UNINITIALIZED, CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(appParameters.GetCommissionableDataProvider() != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(appParameters.GetDeviceAttestationCredentialsProvider() != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(appParameters.GetServerInitParamsProvider() != nullptr, CHIP_ERROR_INVALID_ARGUMENT); @@ -74,14 +74,14 @@ CHIP_ERROR CastingApp::Initialize(const AppParameters & appParameters) } #endif // CHIP_ENABLE_ROTATING_DEVICE_ID - mState = NOT_RUNNING; // initialization done, set state to NOT_RUNNING + mState = CASTING_APP_NOT_RUNNING; // initialization done, set state to NOT_RUNNING return CHIP_NO_ERROR; } CHIP_ERROR CastingApp::Start() { - VerifyOrReturnError(mState == NOT_RUNNING, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mState == CASTING_APP_NOT_RUNNING, CHIP_ERROR_INCORRECT_STATE); // start Matter server chip::ServerInitParams * serverInitParams = mAppParameters->GetServerInitParamsProvider()->Get(); @@ -96,7 +96,7 @@ CHIP_ERROR CastingApp::Start() CHIP_ERROR CastingApp::PostStartRegistrations() { - VerifyOrReturnError(mState == NOT_RUNNING, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mState == CASTING_APP_NOT_RUNNING, CHIP_ERROR_INCORRECT_STATE); auto & server = chip::Server::GetInstance(); // TODO: Set CastingApp as AppDelegate @@ -112,13 +112,13 @@ CHIP_ERROR CastingApp::PostStartRegistrations() // TODO: Add DeviceEvent Handler // ReturnErrorOnFailure(DeviceLayer::PlatformMgrImpl().AddEventHandler(DeviceEventCallback, 0)); - mState = RUNNING; // CastingApp started successfully, set state to RUNNING + mState = CASTING_APP_RUNNING; // CastingApp started successfully, set state to RUNNING return CHIP_NO_ERROR; } CHIP_ERROR CastingApp::Stop() { - VerifyOrReturnError(mState == RUNNING, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mState == CASTING_APP_RUNNING, CHIP_ERROR_INCORRECT_STATE); // TODO: add logic to capture CastingPlayers that we are currently connected to, so we can automatically reconnect with them on // Start() again @@ -126,7 +126,7 @@ CHIP_ERROR CastingApp::Stop() // Shutdown the Matter server chip::Server::GetInstance().Shutdown(); - mState = NOT_RUNNING; // CastingApp stopped successfully, set state to NOT_RUNNING + mState = CASTING_APP_NOT_RUNNING; // CastingApp stopped successfully, set state to NOT_RUNNING return CHIP_ERROR_NOT_IMPLEMENTED; } 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 eed69e728d4358..e66283c3fb1c9d 100644 --- a/examples/tv-casting-app/tv-casting-common/core/CastingApp.h +++ b/examples/tv-casting-app/tv-casting-common/core/CastingApp.h @@ -30,9 +30,9 @@ namespace core { */ enum CastingAppState { - UNINITIALIZED, // Before Initialize() success - NOT_RUNNING, // After Initialize() success before Start()ing, OR After stop() success - RUNNING, // After Start() success + CASTING_APP_UNINITIALIZED, // Before Initialize() success + CASTING_APP_NOT_RUNNING, // After Initialize() success before Start()ing, OR After stop() success + CASTING_APP_RUNNING, // After Start() success }; /** @@ -83,7 +83,7 @@ class CastingApp const matter::casting::support::AppParameters * mAppParameters; - CastingAppState mState = UNINITIALIZED; + CastingAppState mState = CASTING_APP_UNINITIALIZED; }; }; // namespace core diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h new file mode 100644 index 00000000000000..c523fb111b0b48 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayer.h @@ -0,0 +1,168 @@ +/* + * + * 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 "Types.h" +#include "lib/support/logging/CHIPLogging.h" + +#include +#include +#include +#include + +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 +const int kIdMaxLength = chip::Dnssd::kHostNameMaxLength + kPortMaxLength; + +class CastingPlayerAttributes +{ +public: + char id[kIdMaxLength] = {}; + char deviceName[chip::Dnssd::kMaxDeviceNameLen + 1] = {}; + char hostName[chip::Dnssd::kHostNameMaxLength + 1] = {}; + char instanceName[chip::Dnssd::kHostNameMaxLength + 1] = {}; + unsigned int numIPs; // number of valid IP addresses + chip::Inet::IPAddress ipAddresses[chip::Dnssd::CommonResolutionData::kMaxIPAddresses]; + uint16_t port; + uint16_t productId; + uint16_t vendorId; + uint32_t deviceType; +}; + +/** + * @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; } + const char * GetId() const { return mAttributes.id; } + + const char * GetDeviceName() const { return mAttributes.deviceName; } + + const char * GetHostName() const { return mAttributes.hostName; } + + const char * GetInstanceName() const { return mAttributes.instanceName; } + + uint GetNumIPs() const { return mAttributes.numIPs; } + + chip::Inet::IPAddress * GetIPAddresses() { return mAttributes.ipAddresses; } + + uint16_t GetPort() { return mAttributes.port; } + + uint16_t GetProductId() const { return mAttributes.productId; } + + uint16_t GetVendorId() const { return mAttributes.vendorId; } + + uint32_t GetDeviceType() const { return mAttributes.deviceType; } + + // public: + // void RegisterEndpoint(const memory::Strong endpoint) { endpoints.push_back(endpoint); } + + // const std::vector> GetEndpoints() const { return endpoints; } + + /** + * @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; + } + +public: + /** + * @return true if this CastingPlayer is connected to the CastingApp + */ + bool IsConnected() const { return mConnected; } + + void Connect(const long timeout, ConnectCallback onCompleted); + void Disconnect(const long timeout, DisconnectCallback onCompleted); + + 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); + } + } +}; + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp new file mode 100644 index 00000000000000..65a589932c1a4b --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.cpp @@ -0,0 +1,132 @@ +/* + * + * 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 "CastingPlayerDiscovery.h" + +namespace matter { +namespace casting { +namespace core { + +using namespace chip::System; +using namespace chip::Controller; +using namespace chip::Dnssd; +using namespace std; + +CastingPlayerDiscovery * CastingPlayerDiscovery::_castingPlayerDiscovery = nullptr; + +CastingPlayerDiscovery::CastingPlayerDiscovery() {} + +CastingPlayerDiscovery * CastingPlayerDiscovery::GetInstance() +{ + if (_castingPlayerDiscovery == nullptr) + { + _castingPlayerDiscovery = new CastingPlayerDiscovery(); + } + return _castingPlayerDiscovery; +} + +CHIP_ERROR CastingPlayerDiscovery::StartDiscovery(uint64_t deviceTypeFilter) +{ + ChipLogProgress(Discovery, "CastingPlayerDiscovery::StartDiscovery called"); + VerifyOrReturnError(mState == DISCOVERY_READY, CHIP_ERROR_INCORRECT_STATE); + + mCommissionableNodeController.RegisterDeviceDiscoveryDelegate(&mDelegate); + + if (deviceTypeFilter > 0) + { + ReturnErrorOnFailure(mCommissionableNodeController.DiscoverCommissioners( + DiscoveryFilter(DiscoveryFilterType::kDeviceType, deviceTypeFilter))); + } + else + { + ReturnErrorOnFailure(mCommissionableNodeController.DiscoverCommissioners()); + } + + mState = DISCOVERY_RUNNING; + return CHIP_NO_ERROR; +} + +CHIP_ERROR CastingPlayerDiscovery::StopDiscovery() +{ + VerifyOrReturnError(mState == DISCOVERY_RUNNING, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorOnFailure(mCommissionableNodeController.StopDiscovery()); + + mState = DISCOVERY_READY; + + return CHIP_NO_ERROR; +} + +void DeviceDiscoveryDelegateImpl::OnDiscoveredDevice(const chip::Dnssd::DiscoveredNodeData & nodeData) +{ + ChipLogProgress(Discovery, "DeviceDiscoveryDelegateImpl::OnDiscoveredDevice called"); + VerifyOrReturn(mClientDelegate != nullptr, + ChipLogError(NotSpecified, "CastingPlayerDeviceDiscoveryDelegate, mClientDelegate is a nullptr")); + + // convert nodeData to CastingPlayer + CastingPlayerAttributes attributes; + strcpy(attributes.id, nodeData.resolutionData.hostName); + + char port[kPortMaxLength] = {}; + snprintf(port, sizeof(port), "%u", nodeData.resolutionData.port); + strcat(attributes.id, port); + + 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; + + memory::Strong player = std::make_shared(attributes); + + std::vector> castingPlayers = CastingPlayerDiscovery::GetInstance()->GetCastingPlayers(); + + // Add to or update castingPlayers + if (castingPlayers.size() != 0) + { + auto it = + std::find_if(castingPlayers.begin(), castingPlayers.end(), + [&player](const memory::Strong & castingPlayer) { return *castingPlayer == *player; }); + + // ID match found in castingPlayer, perfom update + if (it != castingPlayers.end()) + { + unsigned index = (unsigned int) std::distance(castingPlayers.begin(), it); + castingPlayers[index] = player; + CastingPlayerDiscovery::GetInstance()->mCastingPlayers = castingPlayers; + ChipLogProgress(AppServer, "Updated Casting Player"); + + mClientDelegate->HandleOnUpdated(player); + return; + } + } + + castingPlayers.push_back(player); + CastingPlayerDiscovery::GetInstance()->mCastingPlayers = castingPlayers; + mClientDelegate->HandleOnAdded(player); +} + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h new file mode 100644 index 00000000000000..1c2d3fe1c71033 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/core/CastingPlayerDiscovery.h @@ -0,0 +1,133 @@ +/* + * + * 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 "Types.h" + +#include +#include +#include + +namespace matter { +namespace casting { +namespace core { + +/** + * @brief Represents CastingPlayerDiscovery state. + * + */ +enum CastingPlayerDiscoveryState +{ + DISCOVERY_NOT_READY, // Default state, mClientDelegate isn't initialized + DISCOVERY_READY, // After SetDelegate and before StartDiscovery, mClientDelegate is initialized + DISCOVERY_RUNNING, // After StartDiscovery success +}; + +/** + * @brief DiscoveryDelegate handles callbacks as CastingPlayers are discovered, updated, or lost + * from the network. + */ +class DLL_EXPORT DiscoveryDelegate +{ +public: + virtual ~DiscoveryDelegate() {} + virtual void HandleOnAdded(memory::Strong player) = 0; + virtual void HandleOnUpdated(memory::Strong players) = 0; + // virtual void HandleOnRemoved(memory::Strong players) = 0; +}; + +class CastingPlayerDiscovery; + +/** + * @brief DeviceDiscoveryDelegateImpl defines functionality for when callback + * OnDiscoveredDevice is called. + */ +class DeviceDiscoveryDelegateImpl : public chip::Controller::DeviceDiscoveryDelegate +{ +private: + DiscoveryDelegate * mClientDelegate = nullptr; + +public: + DeviceDiscoveryDelegateImpl() {} + DeviceDiscoveryDelegateImpl(DiscoveryDelegate * delegate) { mClientDelegate = delegate; } + + void OnDiscoveredDevice(const chip::Dnssd::DiscoveredNodeData & nodeData) override; +}; + +/** + * @brief CastingPlayerDiscovery represents the discovery of Casting Players. + * This class is a singleton. + */ +class CastingPlayerDiscovery +{ + +private: + std::vector> mCastingPlayers; + DeviceDiscoveryDelegateImpl mDelegate; + + CastingPlayerDiscovery(); + static CastingPlayerDiscovery * _castingPlayerDiscovery; + + CastingPlayerDiscovery(CastingPlayerDiscovery & other) = delete; + void operator=(const CastingPlayerDiscovery &) = delete; + + chip::Controller::CommissionableNodeController mCommissionableNodeController; + CastingPlayerDiscoveryState mState = DISCOVERY_NOT_READY; + +public: + static CastingPlayerDiscovery * GetInstance(); + + /** + * @brief Starts the discovery for CastingPlayers + * + * @param filterBydeviceType if passed as a non-zero value, CastingPlayerDiscovery will only callback on the DiscoveryDelegate + * with CastingPlayers whose deviceType matches filterBydeviceType + * @return CHIP_ERROR - CHIP_NO_ERROR if discovery for CastingPlayers started successfully, specific error code otherwise. + */ + CHIP_ERROR StartDiscovery(uint64_t filterBydeviceType = 0); + + /** + * @brief Stop the discovery for CastingPlayers + * + * @return CHIP_ERROR - CHIP_NO_ERROR if discovery for CastingPlayers stopped successfully, specific error code otherwise. + */ + CHIP_ERROR StopDiscovery(); + + void SetDelegate(DiscoveryDelegate * clientDelegate) + { + if (clientDelegate == nullptr) + { + mState = DISCOVERY_NOT_READY; + } + else + { + mState = DISCOVERY_READY; + } + mDelegate = DeviceDiscoveryDelegateImpl(clientDelegate); + } + + std::vector> GetCastingPlayers() { return mCastingPlayers; } + + friend class DeviceDiscoveryDelegateImpl; +}; + +}; // namespace core +}; // namespace casting +}; // namespace matter diff --git a/src/controller/AbstractDnssdDiscoveryController.h b/src/controller/AbstractDnssdDiscoveryController.h index 55e148f68d21af..788102282bd86d 100644 --- a/src/controller/AbstractDnssdDiscoveryController.h +++ b/src/controller/AbstractDnssdDiscoveryController.h @@ -43,6 +43,7 @@ class DLL_EXPORT AbstractDnssdDiscoveryController : public Dnssd::CommissioningR ~AbstractDnssdDiscoveryController() override { mDNSResolver.Shutdown(); } void OnNodeDiscovered(const chip::Dnssd::DiscoveredNodeData & nodeData) override; + CHIP_ERROR StopDiscovery() { return mDNSResolver.StopDiscovery(); }; protected: using DiscoveredNodeList = FixedSpan; diff --git a/src/controller/CHIPCommissionableNodeController.cpp b/src/controller/CHIPCommissionableNodeController.cpp index 67e6ce8065c29a..fdcf1f7e2649b9 100644 --- a/src/controller/CHIPCommissionableNodeController.cpp +++ b/src/controller/CHIPCommissionableNodeController.cpp @@ -48,6 +48,16 @@ CHIP_ERROR CommissionableNodeController::DiscoverCommissioners(Dnssd::DiscoveryF return mResolver->DiscoverCommissioners(discoveryFilter); } +CHIP_ERROR CommissionableNodeController::StopDiscovery() +{ + if (mResolver == nullptr) + { + return AbstractDnssdDiscoveryController::StopDiscovery(); + } + + return mResolver->StopDiscovery(); +} + CommissionableNodeController::~CommissionableNodeController() { mDNSResolver.SetCommissioningDelegate(nullptr); diff --git a/src/controller/CHIPCommissionableNodeController.h b/src/controller/CHIPCommissionableNodeController.h index a31e14309af367..e573ba6fc4b5e9 100644 --- a/src/controller/CHIPCommissionableNodeController.h +++ b/src/controller/CHIPCommissionableNodeController.h @@ -42,6 +42,8 @@ class DLL_EXPORT CommissionableNodeController : public AbstractDnssdDiscoveryCon void RegisterDeviceDiscoveryDelegate(DeviceDiscoveryDelegate * delegate) { mDeviceDiscoveryDelegate = delegate; } CHIP_ERROR DiscoverCommissioners(Dnssd::DiscoveryFilter discoveryFilter = Dnssd::DiscoveryFilter()); + CHIP_ERROR StopDiscovery(); + /** * @return * Pointer to DiscoveredNodeData at index idx in the list of commissioners discovered