diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DiscoveredNodeData.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DiscoveredNodeData.java index 2590f82dc2bf92..5674b5f4a6c99d 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DiscoveredNodeData.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/DiscoveredNodeData.java @@ -106,6 +106,18 @@ public DiscoveredNodeData(NsdServiceInfo serviceInfo) { this.numIPs = 1; } + public DiscoveredNodeData(VideoPlayer player) { + this.connectableVideoPlayer = player; + this.instanceName = player.getInstanceName(); + this.hostName = player.getHostName(); + this.deviceName = player.getDeviceName(); + this.deviceType = player.getDeviceType(); + this.vendorId = player.getVendorId(); + this.productId = player.getProductId(); + this.ipAddresses = player.getIpAddresses(); + this.port = player.getPort(); + } + void setConnectableVideoPlayer(VideoPlayer videoPlayer) { this.connectableVideoPlayer = videoPlayer; } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java index f30132ffc9a6ea..9f48d98fa44082 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/TvCastingApp.java @@ -31,6 +31,9 @@ import chip.platform.PreferencesKeyValueStoreManager; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class TvCastingApp { private static final String TAG = TvCastingApp.class.getSimpleName(); @@ -38,6 +41,8 @@ public class TvCastingApp { private static final List DISCOVERY_TARGET_DEVICE_TYPE_FILTER = Arrays.asList(35L); // Video player = 35; + private static final long STR_CACHE_LAST_DISCOVERED_DAYS = 60; + private static TvCastingApp sInstance; private Context applicationContext; private ChipAppServer chipAppServer; @@ -48,6 +53,7 @@ public class TvCastingApp { private WifiManager.MulticastLock multicastLock; private NsdManager nsdManager; private NsdDiscoveryListener nsdDiscoveryListener; + private ScheduledFuture reportSleepingCommissionersFuture; private TvCastingApp() {} @@ -149,17 +155,58 @@ public void discoverVideoPlayerCommissioners( nsdManager.discoverServices( DISCOVERY_TARGET_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, nsdDiscoveryListener); Log.d(TAG, "TvCastingApp.discoverVideoPlayerCommissioners started"); + + this.reportSleepingCommissionersFuture = + Executors.newScheduledThreadPool(1) + .schedule( + () -> { + Log.d( + TAG, + "Scheduling reportSleepingCommissioners with commissioner count " + + (preCommissionedVideoPlayers != null + ? preCommissionedVideoPlayers.size() + : 0)); + reportSleepingCommissioners( + preCommissionedVideoPlayers, discoverySuccessCallback); + }, + 5000, + TimeUnit.MILLISECONDS); + this.discoveryStarted = true; } } + private void reportSleepingCommissioners( + List undiscoveredVideoPlayers, + SuccessCallback discoverySuccessCallback) { + Log.d( + TAG, + "reportSleepingCommissioners called with commissioner count " + + (undiscoveredVideoPlayers != null ? undiscoveredVideoPlayers.size() : 0)); + if (undiscoveredVideoPlayers != null) { + for (VideoPlayer player : undiscoveredVideoPlayers) { + Log.d(TAG, "Undisc Video Player: " + player); + // report a player if we got its MAC address previously and it was recently discoverable + if (player.getMACAddress() != null + /* TODO: add a recency check + && player.getLastDiscoveredMs() + > System.currentTimeMillis() + - STR_CACHE_LAST_DISCOVERED_DAYS * 24 * 60 * 60 * 1000*/ ) { + player.setAsleep(true); + discoverySuccessCallback.handle(new DiscoveredNodeData(player)); + } + } + } + } + public void stopVideoPlayerDiscovery() { synchronized (discoveryLock) { Log.d(TAG, "TvCastingApp trying to stop video player discovery"); if (this.discoveryStarted && nsdManager != null && multicastLock != null - && nsdDiscoveryListener != null) { + && nsdDiscoveryListener != null + && reportSleepingCommissionersFuture != null) { Log.d(TAG, "TvCastingApp stopping Video Player commissioner discovery"); try { nsdManager.stopServiceDiscovery(nsdDiscoveryListener); @@ -173,6 +220,8 @@ public void stopVideoPlayerDiscovery() { if (multicastLock.isHeld()) { multicastLock.release(); } + + reportSleepingCommissionersFuture.cancel(false); this.discoveryStarted = false; } } @@ -192,12 +241,61 @@ public native boolean openBasicCommissioningWindow( public native List readCachedVideoPlayers(); - public native boolean verifyOrEstablishConnection( + public boolean verifyOrEstablishConnection( + VideoPlayer targetVideoPlayer, + SuccessCallback onConnectionSuccess, + FailureCallback onConnectionFailure, + SuccessCallback onNewOrUpdatedEndpointCallback) { + Log.d(TAG, "TvCastingApp.verifyOrEstablishConnection called"); + if (!targetVideoPlayer.isAsleep()) { + // if the player is NOT asleep, establish connection right away + return _verifyOrEstablishConnection( + targetVideoPlayer, + onConnectionSuccess, + onConnectionFailure, + onNewOrUpdatedEndpointCallback); + } else { + boolean status = false; + // targetVideoPlayer is asleep. Send WakeOnLAN packet to it + if (_sendWakeOnLAN(targetVideoPlayer)) { + status = true; + // wait for player to turn on, then establish connection + Log.d(TAG, "Scheduling delayed call to _verifyOrEstablishConnection after sending WoL"); + Executors.newScheduledThreadPool(1) + .schedule( + () -> { + boolean callStatus = + _verifyOrEstablishConnection( + targetVideoPlayer, + new SuccessCallback() { + @Override + public void handle(VideoPlayer response) { + response.setAsleep(false); + onConnectionSuccess.handle(response); + } + }, + onConnectionFailure, + onNewOrUpdatedEndpointCallback); + if (callStatus == false) { + Log.e(TAG, "_verifyOrEstablishConnection failed after attempting to WoL"); + onConnectionFailure.handle(new MatterError(0x03, "CHIP_ERROR_INCORRECT_STATE")); + } + }, + 10000, + TimeUnit.MILLISECONDS); + } + return status; + } + } + + private native boolean _verifyOrEstablishConnection( VideoPlayer targetVideoPlayer, SuccessCallback onConnectionSuccess, FailureCallback onConnectionFailure, SuccessCallback onNewOrUpdatedEndpointCallback); + private native boolean _sendWakeOnLAN(VideoPlayer targetVideoPlayer); + public native void shutdownAllSubscriptions(); public native void disconnect(); diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/VideoPlayer.java b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/VideoPlayer.java index e440acdce093d4..f45a5e19cf1fc3 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/VideoPlayer.java +++ b/examples/tv-casting-app/android/App/app/src/main/jni/com/chip/casting/VideoPlayer.java @@ -29,15 +29,20 @@ public class VideoPlayer { private long nodeId; private byte fabricIndex; private String deviceName; + private String instanceName; private int vendorId; private int productId; private int deviceType; private List contentApps; + private long lastDiscoveredMs; + private String MACAddress; + private boolean isAsleep = false; private boolean isConnected = false; private int numIPs; private List ipAddresses; private String hostName; + private int port; private boolean isInitialized = false; @@ -52,6 +57,10 @@ public VideoPlayer( int numIPs, List ipAddresses, String hostName, + String instanceName, + int port, + long lastDiscoveredMs, + String MACAddress, boolean isConnected) { this.nodeId = nodeId; this.fabricIndex = fabricIndex; @@ -64,6 +73,10 @@ public VideoPlayer( this.numIPs = numIPs; this.ipAddresses = ipAddresses; this.hostName = hostName; + this.MACAddress = MACAddress; + this.lastDiscoveredMs = lastDiscoveredMs; + this.instanceName = instanceName; + this.port = port; this.isInitialized = true; } @@ -116,8 +129,8 @@ public int hashCode() { return Objects.hash(super.hashCode(), nodeId, fabricIndex); } - @java.lang.Override - public java.lang.String toString() { + @Override + public String toString() { return "VideoPlayer{" + "nodeId=" + nodeId @@ -126,6 +139,9 @@ public java.lang.String toString() { + ", deviceName='" + deviceName + '\'' + + ", instanceName='" + + instanceName + + '\'' + ", vendorId=" + vendorId + ", productId=" @@ -134,16 +150,25 @@ public java.lang.String toString() { + deviceType + ", contentApps=" + contentApps + + ", lastDiscoveredMs=" + + lastDiscoveredMs + + ", MACAddress='" + + MACAddress + + '\'' + + ", isAsleep=" + + isAsleep + ", isConnected=" + isConnected + ", numIPs=" + numIPs + ", ipAddresses=" + ipAddresses - + ", isInitialized=" + ", hostName='" + hostName + '\'' + + ", port=" + + port + + ", isInitialized=" + isInitialized + '}'; } @@ -180,6 +205,42 @@ public int getDeviceType() { return deviceType; } + public int getNumIPs() { + return numIPs; + } + + public List getIpAddresses() { + return ipAddresses; + } + + public String getHostName() { + return hostName; + } + + public int getPort() { + return port; + } + + public long getLastDiscoveredMs() { + return lastDiscoveredMs; + } + + public String getMACAddress() { + return MACAddress; + } + + public String getInstanceName() { + return instanceName; + } + + public void setAsleep(boolean asleep) { + isAsleep = asleep; + } + + public boolean isAsleep() { + return isAsleep; + } + public boolean isInitialized() { return isInitialized; } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/ConversionUtils.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/ConversionUtils.cpp index e1e174d940c007..3a16df930c4afd 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/ConversionUtils.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/ConversionUtils.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include CHIP_ERROR convertJAppParametersToCppAppParams(jobject appParameters, AppParams & outAppParams) { @@ -147,6 +149,9 @@ CHIP_ERROR convertJVideoPlayerToTargetVideoPlayerInfo(jobject videoPlayer, Targe jfieldID jDeviceType = env->GetFieldID(jVideoPlayerClass, "deviceType", "I"); uint16_t deviceType = static_cast(env->GetIntField(videoPlayer, jDeviceType)); + jfieldID jPort = env->GetFieldID(jVideoPlayerClass, "port", "I"); + uint16_t port = static_cast(env->GetIntField(videoPlayer, jPort)); + jfieldID getDeviceNameField = env->GetFieldID(jVideoPlayerClass, "deviceName", "Ljava/lang/String;"); jstring jDeviceName = static_cast(env->GetObjectField(videoPlayer, getDeviceNameField)); const char * deviceName = env->GetStringUTFChars(jDeviceName, 0); @@ -155,8 +160,25 @@ CHIP_ERROR convertJVideoPlayerToTargetVideoPlayerInfo(jobject videoPlayer, Targe jstring jHostName = static_cast(env->GetObjectField(videoPlayer, getHostNameField)); const char * hostName = env->GetStringUTFChars(jHostName, 0); + jfieldID getInstanceNameField = env->GetFieldID(jVideoPlayerClass, "instanceName", "Ljava/lang/String;"); + jstring jInstanceName = static_cast(env->GetObjectField(videoPlayer, getInstanceNameField)); + const char * instanceName = env->GetStringUTFChars(jInstanceName, 0); + + jfieldID jLastDiscoveredMs = env->GetFieldID(jVideoPlayerClass, "lastDiscoveredMs", "J"); + long lastDiscoveredMs = static_cast(env->GetLongField(videoPlayer, jLastDiscoveredMs)); + + jfieldID getMACAddressField = env->GetFieldID(jVideoPlayerClass, "MACAddress", "Ljava/lang/String;"); + jstring jMACAddress = static_cast(env->GetObjectField(videoPlayer, getMACAddressField)); + const char * MACAddress = env->GetStringUTFChars(jMACAddress, 0); + outTargetVideoPlayerInfo.Initialize(nodeId, fabricIndex, nullptr, nullptr, vendorId, productId, deviceType, deviceName, - hostName); + hostName, 0, nullptr, port, instanceName, chip::System::Clock::Timestamp(lastDiscoveredMs)); + + if (MACAddress != nullptr) + { + chip::CharSpan MACAddressSpan(MACAddress, strlen(MACAddress) - 1); + outTargetVideoPlayerInfo.SetMACAddress(MACAddressSpan); + } jfieldID jContentAppsField = env->GetFieldID(jVideoPlayerClass, "contentApps", "Ljava/util/List;"); jobject jContentApps = env->GetObjectField(videoPlayer, jContentAppsField); @@ -197,7 +219,8 @@ CHIP_ERROR convertTargetVideoPlayerInfoToJVideoPlayer(TargetVideoPlayerInfo * ta ReturnErrorOnFailure( chip::JniReferences::GetInstance().GetClassRef(env, "com/chip/casting/VideoPlayer", jVideoPlayerClass)); jmethodID jVideoPlayerConstructor = env->GetMethodID( - jVideoPlayerClass, "", "(JBLjava/lang/String;IIILjava/util/List;ILjava/util/List;Ljava/lang/String;Z)V"); + jVideoPlayerClass, "", + "(JBLjava/lang/String;IIILjava/util/List;ILjava/util/List;Ljava/lang/String;Ljava/lang/String;IJLjava/lang/String;Z)V"); jobject jContentAppList = nullptr; TargetEndpointInfo * endpoints = targetVideoPlayerInfo->GetEndpoints(); @@ -218,6 +241,20 @@ CHIP_ERROR convertTargetVideoPlayerInfoToJVideoPlayer(TargetVideoPlayerInfo * ta jstring hostName = targetVideoPlayerInfo->GetHostName() == nullptr ? nullptr : env->NewStringUTF(targetVideoPlayerInfo->GetHostName()); + jstring instanceName = targetVideoPlayerInfo->GetInstanceName() == nullptr + ? nullptr + : env->NewStringUTF(targetVideoPlayerInfo->GetInstanceName()); + + jstring MACAddress = nullptr; + if (targetVideoPlayerInfo->GetMACAddress() != nullptr && targetVideoPlayerInfo->GetMACAddress()->data() != nullptr) + { + char MACAddressWithNil[2 * chip::DeviceLayer::ConfigurationManager::kMaxMACAddressLength + 1]; + strncpy(MACAddressWithNil, targetVideoPlayerInfo->GetMACAddress()->data(), + targetVideoPlayerInfo->GetMACAddress()->size()); + MACAddressWithNil[targetVideoPlayerInfo->GetMACAddress()->size()] = '\0'; + MACAddress = env->NewStringUTF(MACAddressWithNil); + } + jobject jIPAddressList = nullptr; const chip::Inet::IPAddress * ipAddresses = targetVideoPlayerInfo->GetIpAddresses(); if (ipAddresses != nullptr) @@ -243,8 +280,9 @@ CHIP_ERROR convertTargetVideoPlayerInfoToJVideoPlayer(TargetVideoPlayerInfo * ta outVideoPlayer = env->NewObject(jVideoPlayerClass, jVideoPlayerConstructor, targetVideoPlayerInfo->GetNodeId(), targetVideoPlayerInfo->GetFabricIndex(), deviceName, targetVideoPlayerInfo->GetVendorId(), targetVideoPlayerInfo->GetProductId(), targetVideoPlayerInfo->GetDeviceType(), - jContentAppList, targetVideoPlayerInfo->GetNumIPs(), jIPAddressList, hostName, - targetVideoPlayerInfo->GetOperationalDeviceProxy() != nullptr); + jContentAppList, targetVideoPlayerInfo->GetNumIPs(), jIPAddressList, hostName, instanceName, + targetVideoPlayerInfo->GetPort(), targetVideoPlayerInfo->GetLastDiscovered().count(), + MACAddress, targetVideoPlayerInfo->GetOperationalDeviceProxy() != nullptr); } return CHIP_NO_ERROR; } diff --git a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp index 5293cc31305fd5..f5ef22cb346bab 100644 --- a/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp +++ b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp @@ -243,13 +243,13 @@ JNI_METHOD(jobject, readCachedVideoPlayers)(JNIEnv * env, jobject) return jVideoPlayerList; } -JNI_METHOD(jboolean, verifyOrEstablishConnection) +JNI_METHOD(jboolean, _1verifyOrEstablishConnection) (JNIEnv * env, jobject, jobject videoPlayer, jobject jOnConnectionSuccessHandler, jobject jOnConnectionFailureHandler, jobject jOnNewOrUpdatedEndpointHandler) { chip::DeviceLayer::StackLock lock; - ChipLogProgress(AppServer, "JNI_METHOD verifyOrEstablishConnection called"); + ChipLogProgress(AppServer, "JNI_METHOD _1verifyOrEstablishConnection called"); TargetVideoPlayerInfo targetVideoPlayerInfo; CHIP_ERROR err = convertJVideoPlayerToTargetVideoPlayerInfo(videoPlayer, targetVideoPlayerInfo); @@ -276,7 +276,29 @@ JNI_METHOD(jboolean, verifyOrEstablishConnection) [](CHIP_ERROR err) { TvCastingAppJNIMgr().getOnConnectionFailureHandler(true).Handle(err); }, [](TargetEndpointInfo * endpoint) { TvCastingAppJNIMgr().getOnNewOrUpdatedEndpointHandler(true).Handle(endpoint); }); VerifyOrExit(CHIP_NO_ERROR == err, - ChipLogError(AppServer, "CastingServer::verifyOrEstablishConnection failed: %" CHIP_ERROR_FORMAT, err.Format())); + ChipLogError(AppServer, "CastingServer::_1verifyOrEstablishConnection failed: %" CHIP_ERROR_FORMAT, err.Format())); + +exit: + return (err == CHIP_NO_ERROR); +} + +JNI_METHOD(jboolean, _1sendWakeOnLAN) +(JNIEnv * env, jobject, jobject videoPlayer) +{ + chip::DeviceLayer::StackLock lock; + + ChipLogProgress(AppServer, "JNI_METHOD _1sendWakeOnLAN called"); + + TargetVideoPlayerInfo targetVideoPlayerInfo; + CHIP_ERROR err = convertJVideoPlayerToTargetVideoPlayerInfo(videoPlayer, targetVideoPlayerInfo); + VerifyOrExit(err == CHIP_NO_ERROR, + ChipLogError(AppServer, + "Conversion from jobject VideoPlayer to TargetVideoPlayerInfo * failed: %" CHIP_ERROR_FORMAT, + err.Format())); + + err = CastingServer::GetInstance()->SendWakeOnLAN(targetVideoPlayerInfo); + VerifyOrExit(CHIP_NO_ERROR == err, + ChipLogError(AppServer, "CastingServer::_sendWakeOnLAN failed: %" CHIP_ERROR_FORMAT, err.Format())); exit: return (err == CHIP_NO_ERROR); diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CastingServerBridge.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CastingServerBridge.mm index 3f4dbce0700bb7..c25aebc3f98b2f 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CastingServerBridge.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CastingServerBridge.mm @@ -31,6 +31,7 @@ #include #include #include +#include #ifndef CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE #define CHIP_DEVICE_CONFIG_USE_TEST_SETUP_PIN_CODE 20202021 @@ -749,38 +750,49 @@ - (void)verifyOrEstablishConnection:(VideoPlayer * _Nonnull)videoPlayer [ConversionUtils convertToCppTargetVideoPlayerInfoFrom:videoPlayer outTargetVideoPlayerInfo:targetVideoPlayerInfo]; - CHIP_ERROR err = CastingServer::GetInstance()->VerifyOrEstablishConnection( - targetVideoPlayerInfo, - [clientQueue, onConnectionSuccessCallback](TargetVideoPlayerInfo * cppTargetVideoPlayerInfo) { - VideoPlayer * videoPlayer = - [ConversionUtils convertToObjCVideoPlayerFrom:cppTargetVideoPlayerInfo]; - [[CastingServerBridge getSharedInstance] - dispatchOnClientQueue:clientQueue - description:@"onConnectionSuccessCallback" - block:^{ - onConnectionSuccessCallback(videoPlayer); - }]; - }, - [clientQueue, onConnectionFailureCallback](CHIP_ERROR err) { - [[CastingServerBridge getSharedInstance] - dispatchOnClientQueue:clientQueue - description:@"onConnectionFailureCallback" - block:^{ - onConnectionFailureCallback([[MatterError alloc] - initWithCode:err.AsInteger() - message:[NSString stringWithUTF8String:err.AsString()]]); - }]; - }, - [clientQueue, onNewOrUpdatedEndpointCallback](TargetEndpointInfo * cppTargetEndpointInfo) { - ContentApp * contentApp = - [ConversionUtils convertToObjCContentAppFrom:cppTargetEndpointInfo]; - [[CastingServerBridge getSharedInstance] - dispatchOnClientQueue:clientQueue - description:@"onNewOrUpdatedEndpointCallback" - block:^{ - onNewOrUpdatedEndpointCallback(contentApp); - }]; - }); + CHIP_ERROR err = CHIP_NO_ERROR; + if (videoPlayer.isAsleep) { + ChipLogProgress(AppServer, "verifyOrEstablishConnection called on a sleeping VideoPlayer"); + err = CastingServer::GetInstance()->SendWakeOnLAN(targetVideoPlayerInfo); + usleep(10 * 1000 * 1000); // 10sec + } + + if (err == CHIP_NO_ERROR) { + err = CastingServer::GetInstance()->VerifyOrEstablishConnection( + targetVideoPlayerInfo, + [clientQueue, onConnectionSuccessCallback]( + TargetVideoPlayerInfo * cppTargetVideoPlayerInfo) { + VideoPlayer * videoPlayer = + [ConversionUtils convertToObjCVideoPlayerFrom:cppTargetVideoPlayerInfo]; + [[CastingServerBridge getSharedInstance] + dispatchOnClientQueue:clientQueue + description:@"onConnectionSuccessCallback" + block:^{ + onConnectionSuccessCallback(videoPlayer); + }]; + }, + [clientQueue, onConnectionFailureCallback](CHIP_ERROR err) { + [[CastingServerBridge getSharedInstance] + dispatchOnClientQueue:clientQueue + description:@"onConnectionFailureCallback" + block:^{ + onConnectionFailureCallback([[MatterError alloc] + initWithCode:err.AsInteger() + message:[NSString + stringWithUTF8String:err.AsString()]]); + }]; + }, + [clientQueue, onNewOrUpdatedEndpointCallback](TargetEndpointInfo * cppTargetEndpointInfo) { + ContentApp * contentApp = + [ConversionUtils convertToObjCContentAppFrom:cppTargetEndpointInfo]; + [[CastingServerBridge getSharedInstance] + dispatchOnClientQueue:clientQueue + description:@"onNewOrUpdatedEndpointCallback" + block:^{ + onNewOrUpdatedEndpointCallback(contentApp); + }]; + }); + } dispatch_async(clientQueue, ^{ requestSentHandler( diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CommissionerDiscoveryDelegateImpl.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CommissionerDiscoveryDelegateImpl.h index 3d54b9acc022c6..984be998ea2ed1 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CommissionerDiscoveryDelegateImpl.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CommissionerDiscoveryDelegateImpl.h @@ -21,6 +21,7 @@ #import "ConversionUtils.hpp" #include +#include class CommissionerDiscoveryDelegateImpl : public chip::Controller::DeviceDiscoveryDelegate { public: @@ -31,6 +32,16 @@ class CommissionerDiscoveryDelegateImpl : public chip::Controller::DeviceDiscove mClientQueue = clientQueue; mObjCDiscoveredCommissionerHandler = objCDiscoveredCommissionerHandler; mCachedTargetVideoPlayerInfos = cachedTargetVideoPlayerInfos; + mDiscoveredCommissioners.clear(); + + // cancel preexisting timer for ReportSleepingCommissioners, if any and schedule a new one if there are any cached + // commissioners + chip::DeviceLayer::SystemLayer().CancelTimer(ReportSleepingCommissioners, this); + if (mCachedTargetVideoPlayerInfos != nullptr && mCachedTargetVideoPlayerInfos[0].IsInitialized()) { + chip::DeviceLayer::SystemLayer().StartTimer( + chip::System::Clock::Milliseconds32(CHIP_DEVICE_CONFIG_STR_DISCOVERY_DELAY_SEC * 1000), ReportSleepingCommissioners, + this); + } } void OnDiscoveredDevice(const chip::Dnssd::DiscoveredNodeData & nodeData) @@ -39,11 +50,17 @@ class CommissionerDiscoveryDelegateImpl : public chip::Controller::DeviceDiscove __block const chip::Dnssd::DiscoveredNodeData cppNodeData = nodeData; dispatch_async(mClientQueue, ^{ DiscoveredNodeData * objCDiscoveredNodeData = [ConversionUtils convertToObjCDiscoveredNodeDataFrom:&cppNodeData]; + mDiscoveredCommissioners.push_back(objCDiscoveredNodeData); // add to the list of discovered commissioners // set associated connectable video player from cache, if any if (mCachedTargetVideoPlayerInfos != nullptr) { for (size_t i = 0; i < kMaxCachedVideoPlayers && mCachedTargetVideoPlayerInfos[i].IsInitialized(); i++) { if (mCachedTargetVideoPlayerInfos[i].IsSameAs(&cppNodeData)) { + mCachedTargetVideoPlayerInfos[i].SetLastDiscovered( + chip::System::SystemClock().GetMonotonicMilliseconds64()); // add discovery timestamp + CastingServer::GetInstance()->AddVideoPlayer( + &mCachedTargetVideoPlayerInfos[i]); // write updated video player to cache + VideoPlayer * connectableVideoPlayer = [ConversionUtils convertToObjCVideoPlayerFrom:&mCachedTargetVideoPlayerInfos[i]]; [objCDiscoveredNodeData setConnectableVideoPlayer:connectableVideoPlayer]; @@ -56,10 +73,47 @@ class CommissionerDiscoveryDelegateImpl : public chip::Controller::DeviceDiscove }); } + static void ReportSleepingCommissioners(chip::System::Layer * _Nonnull aSystemLayer, void * _Nullable context) + { + ChipLogProgress(AppServer, "CommissionerDiscoveryDelegateImpl().ReportSleepingCommissioners() called"); + CommissionerDiscoveryDelegateImpl * thiz = (CommissionerDiscoveryDelegateImpl *) context; + if (thiz != nullptr && thiz->mCachedTargetVideoPlayerInfos != nullptr) { + for (size_t i = 0; i < kMaxCachedVideoPlayers && thiz->mCachedTargetVideoPlayerInfos[i].IsInitialized(); i++) { + // check if there is a MACAddress to wake this Video Player up with + if (thiz->mCachedTargetVideoPlayerInfos[i].GetMACAddress() != nullptr + && thiz->mCachedTargetVideoPlayerInfos[i].GetMACAddress()->size() > 0) { + bool discovered = false; + // check if it was already discovered + for (DiscoveredNodeData * discoveredCommissioner : thiz->mDiscoveredCommissioners) { + if (strcmp((char *) [discoveredCommissioner.hostName UTF8String], + thiz->mCachedTargetVideoPlayerInfos[i].GetHostName()) + == 0) { + discovered = true; + break; + } + } + + // surface the *sleeping* video player as a DiscoveredNodeData + if (!discovered) { + DiscoveredNodeData * objCDiscoveredNodeData = + [ConversionUtils convertToDiscoveredNodeDataFrom:&thiz->mCachedTargetVideoPlayerInfos[i]]; + objCDiscoveredNodeData.getConnectableVideoPlayer.isAsleep = true; + + // make the callback + ChipLogProgress(AppServer, + "CommissionerDiscoveryDelegateImpl().ReportSleepingCommissioners() reporting sleeping video player"); + thiz->mObjCDiscoveredCommissionerHandler(objCDiscoveredNodeData); + } + } + } + } + } + private: void (^_Nonnull mObjCDiscoveredCommissionerHandler)(DiscoveredNodeData * _Nonnull); dispatch_queue_t _Nonnull mClientQueue; TargetVideoPlayerInfo * _Nullable mCachedTargetVideoPlayerInfos; + std::vector mDiscoveredCommissioners; }; #endif /* CommissionerDiscoveryDelegateImpl_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.hpp b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.hpp index bf92cf09a0069b..32ced1054beed6 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.hpp +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.hpp @@ -55,6 +55,11 @@ + (VideoPlayer * _Nonnull)convertToObjCVideoPlayerFrom:(TargetVideoPlayerInfo * _Nonnull)cppTargetVideoPlayerInfo; +/** + * @brief inter-object converters + */ ++ (DiscoveredNodeData *)convertToDiscoveredNodeDataFrom:(TargetVideoPlayerInfo * _Nonnull)cppTargetVideoPlayerInfo; + @end #endif /* ConversionUtils_h */ diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.mm b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.mm index a50195987c89ff..06f5b53e617953 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.mm @@ -84,7 +84,14 @@ + (CHIP_ERROR)convertToCppTargetVideoPlayerInfoFrom:(VideoPlayer * _Nonnull)objC { VerifyOrReturnError(objCVideoPlayer.isInitialized, CHIP_ERROR_INVALID_ARGUMENT); ReturnErrorOnFailure(outTargetVideoPlayerInfo.Initialize(objCVideoPlayer.nodeId, objCVideoPlayer.fabricIndex, nullptr, nullptr, - objCVideoPlayer.vendorId, objCVideoPlayer.productId, objCVideoPlayer.deviceType, [objCVideoPlayer.deviceName UTF8String])); + objCVideoPlayer.vendorId, objCVideoPlayer.productId, objCVideoPlayer.deviceType, [objCVideoPlayer.deviceName UTF8String], + [objCVideoPlayer.hostName UTF8String], 0, nullptr, objCVideoPlayer.port, [objCVideoPlayer.instanceName UTF8String], + chip::System::Clock::Timestamp(objCVideoPlayer.lastDiscoveredMs))); + if (objCVideoPlayer.MACAddress != nil) { + outTargetVideoPlayerInfo.SetMACAddress( + chip::CharSpan([objCVideoPlayer.MACAddress UTF8String], objCVideoPlayer.MACAddress.length)); + } + for (ContentApp * contentApp in objCVideoPlayer.contentApps) { TargetEndpointInfo * endpoint = outTargetVideoPlayerInfo.GetOrAddEndpoint(contentApp.endpointId); VerifyOrReturnError(endpoint != nullptr, CHIP_ERROR_INCORRECT_STATE); @@ -143,6 +150,37 @@ + (DiscoveredNodeData *)convertToObjCDiscoveredNodeDataFrom:(const chip::Dnssd:: return objCDiscoveredNodeData; } ++ (DiscoveredNodeData *)convertToDiscoveredNodeDataFrom:(TargetVideoPlayerInfo * _Nonnull)cppTargetVideoPlayerInfo +{ + DiscoveredNodeData * objCDiscoveredNodeData = [DiscoveredNodeData new]; + + objCDiscoveredNodeData.deviceType = cppTargetVideoPlayerInfo->GetDeviceType(); + objCDiscoveredNodeData.vendorId = cppTargetVideoPlayerInfo->GetVendorId(); + objCDiscoveredNodeData.productId = cppTargetVideoPlayerInfo->GetProductId(); + objCDiscoveredNodeData.deviceName = [NSString stringWithCString:cppTargetVideoPlayerInfo->GetDeviceName() + encoding:NSUTF8StringEncoding]; + objCDiscoveredNodeData.instanceName = [NSString stringWithCString:cppTargetVideoPlayerInfo->GetInstanceName() + encoding:NSUTF8StringEncoding]; + + // from CommonResolutionData + objCDiscoveredNodeData.port = cppTargetVideoPlayerInfo->GetPort(); + objCDiscoveredNodeData.hostName = [NSString stringWithCString:cppTargetVideoPlayerInfo->GetHostName() + encoding:NSUTF8StringEncoding]; + objCDiscoveredNodeData.numIPs = cppTargetVideoPlayerInfo->GetNumIPs(); + if (cppTargetVideoPlayerInfo->GetNumIPs() > 0) { + objCDiscoveredNodeData.ipAddresses = [NSMutableArray new]; + } + for (size_t i = 0; i < cppTargetVideoPlayerInfo->GetNumIPs(); i++) { + char addrCString[chip::Inet::IPAddress::kMaxStringLength]; + cppTargetVideoPlayerInfo->GetIpAddresses()[i].ToString(addrCString, chip::Inet::IPAddress::kMaxStringLength); + objCDiscoveredNodeData.ipAddresses[i] = [NSString stringWithCString:addrCString encoding:NSASCIIStringEncoding]; + } + + VideoPlayer * connectableVideoPlayer = [ConversionUtils convertToObjCVideoPlayerFrom:cppTargetVideoPlayerInfo]; + [objCDiscoveredNodeData setConnectableVideoPlayer:connectableVideoPlayer]; + return objCDiscoveredNodeData; +} + + (VideoPlayer *)convertToObjCVideoPlayerFrom:(TargetVideoPlayerInfo * _Nonnull)cppTargetVideoPlayerInfo { VideoPlayer * objCVideoPlayer = [VideoPlayer new]; @@ -155,6 +193,17 @@ + (VideoPlayer *)convertToObjCVideoPlayerFrom:(TargetVideoPlayerInfo * _Nonnull) objCVideoPlayer.isConnected = (cppTargetVideoPlayerInfo->GetOperationalDeviceProxy() != nil); objCVideoPlayer.deviceName = [NSString stringWithCString:cppTargetVideoPlayerInfo->GetDeviceName() encoding:NSUTF8StringEncoding]; + objCVideoPlayer.port = cppTargetVideoPlayerInfo->GetPort(); + if (cppTargetVideoPlayerInfo->GetMACAddress() != nullptr && cppTargetVideoPlayerInfo->GetMACAddress()->size() > 0) { + objCVideoPlayer.MACAddress = [NSString stringWithCString:cppTargetVideoPlayerInfo->GetMACAddress()->data() + encoding:NSUTF8StringEncoding]; + } + objCVideoPlayer.lastDiscoveredMs = cppTargetVideoPlayerInfo->GetLastDiscovered().count(); + objCVideoPlayer.instanceName = [NSString stringWithCString:cppTargetVideoPlayerInfo->GetInstanceName() + encoding:NSUTF8StringEncoding]; + objCVideoPlayer.hostName = [NSString stringWithCString:cppTargetVideoPlayerInfo->GetHostName() + encoding:NSUTF8StringEncoding]; + objCVideoPlayer.contentApps = [NSMutableArray new]; TargetEndpointInfo * cppTargetEndpointInfos = cppTargetVideoPlayerInfo->GetEndpoints(); for (size_t i = 0; i < kMaxNumberOfEndpoints && cppTargetEndpointInfos[i].IsInitialized(); i++) { diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/VideoPlayer.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/VideoPlayer.h index 64a320982a86f6..1496ff9b180d1c 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/VideoPlayer.h +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/VideoPlayer.h @@ -42,6 +42,18 @@ @property uint16_t deviceType; +@property NSString * hostName; + +@property NSString * instanceName; + +@property uint16_t port; + +@property NSString * MACAddress; + +@property uint64_t lastDiscoveredMs; + +@property bool isAsleep; + /** @brief true, if all the required fields are initialized, false otherwise */ @@ -54,7 +66,12 @@ deviceName:(NSString *)deviceName vendorId:(uint16_t)vendorId productId:(uint16_t)productId - deviceType:(uint16_t)deviceType; + deviceType:(uint16_t)deviceType + hostName:(NSString *)hostName + instanceName:(NSString *)instanceName + port:(uint16_t)port + MACAddress:(NSString *)MACAddress + lastDiscoveredMs:(uint64_t)lastDiscoveredMs; @end diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/VideoPlayer.m b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/VideoPlayer.m index 2640fbb2045de0..ef3a3d23f76c65 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/VideoPlayer.m +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/VideoPlayer.m @@ -36,7 +36,12 @@ - (instancetype)initWithNodeId:(uint64_t)nodeId deviceName:(NSString *)deviceName vendorId:(uint16_t)vendorId productId:(uint16_t)productId - deviceType:(uint16_t)deviceType; + deviceType:(uint16_t)deviceType + hostName:(NSString *)hostName + instanceName:(NSString *)instanceName + port:(uint16_t)port + MACAddress:(NSString *)MACAddress + lastDiscoveredMs:(uint64_t)lastDiscoveredMs { if (self = [super init]) { _nodeId = nodeId; @@ -47,6 +52,12 @@ - (instancetype)initWithNodeId:(uint64_t)nodeId _vendorId = vendorId; _productId = productId; _deviceType = deviceType; + _hostName = hostName; + _instanceName = instanceName; + _port = port; + _MACAddress = MACAddress; + _lastDiscoveredMs = lastDiscoveredMs; + _isAsleep = false; _isInitialized = true; } return self; diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index 115a0b7e4bbfb0..2b0fe46ea48934 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -70,6 +70,7 @@ chip_data_model("tv-casting-common") { "include/TargetEndpointInfo.h", "include/TargetNavigator.h", "include/TargetVideoPlayerInfo.h", + "include/WakeOnLan.h", "src/AppParams.cpp", "src/ApplicationLauncher.cpp", "src/CastingServer.cpp", diff --git a/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h b/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h index a07482b749a4a1..8b3b69ed8efcfc 100644 --- a/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h +++ b/examples/tv-casting-app/tv-casting-common/include/CHIPProjectAppConfig.h @@ -65,6 +65,11 @@ #define CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT 4 +// cached players that were seen before this window (in days) will not be surfaced as "discovered" +#define CHIP_DEVICE_CONFIG_STR_CACHE_LAST_DISCOVERED_DAYS 60L + +#define CHIP_DEVICE_CONFIG_STR_DISCOVERY_DELAY_SEC 5 + // Include the CHIPProjectConfig from config/standalone // Add this at the end so that we can hit our #defines first #include diff --git a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h index fd427afc712639..7b71d83aa5698d 100644 --- a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h +++ b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h @@ -32,6 +32,7 @@ #include "TargetEndpointInfo.h" #include "TargetNavigator.h" #include "TargetVideoPlayerInfo.h" +#include "WakeOnLan.h" #include #include @@ -72,6 +73,8 @@ class CastingServer : public AppDelegate CHIP_ERROR SendUserDirectedCommissioningRequest(chip::Dnssd::DiscoveredNodeData * selectedCommissioner); #endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT + CHIP_ERROR SendWakeOnLAN(TargetVideoPlayerInfo & targetVideoPlayerInfo); + TargetVideoPlayerInfo * GetActiveTargetVideoPlayer() { return &mActiveTargetVideoPlayerInfo; } CHIP_ERROR TargetVideoPlayerInfoInit(chip::NodeId nodeId, chip::FabricIndex fabricIndex, @@ -100,6 +103,8 @@ class CastingServer : public AppDelegate std::function onNewOrUpdatedEndpoint); void LogCachedVideoPlayers(); + CHIP_ERROR AddVideoPlayer(TargetVideoPlayerInfo * targetVideoPlayerInfo); + CHIP_ERROR PurgeCache(); /** @@ -455,14 +460,17 @@ class CastingServer : public AppDelegate PersistenceManager mPersistenceManager; bool mInited = false; bool mUdcInProgress = false; + chip::Dnssd::DiscoveredNodeData mStrNodeDataList[kMaxCachedVideoPlayers]; TargetVideoPlayerInfo mActiveTargetVideoPlayerInfo; TargetVideoPlayerInfo mCachedTargetVideoPlayerInfo[kMaxCachedVideoPlayers]; - uint16_t mTargetVideoPlayerVendorId = 0; - uint16_t mTargetVideoPlayerProductId = 0; - chip::DeviceTypeId mTargetVideoPlayerDeviceType = 0; - char mTargetVideoPlayerDeviceName[chip::Dnssd::kMaxDeviceNameLen + 1] = {}; - char mTargetVideoPlayerHostName[chip::Dnssd::kHostNameMaxLength + 1] = {}; - size_t mTargetVideoPlayerNumIPs = 0; // number of valid IP addresses + uint16_t mTargetVideoPlayerVendorId = 0; + uint16_t mTargetVideoPlayerProductId = 0; + uint16_t mTargetVideoPlayerPort = 0; + chip::DeviceTypeId mTargetVideoPlayerDeviceType = 0; + char mTargetVideoPlayerDeviceName[chip::Dnssd::kMaxDeviceNameLen + 1] = {}; + char mTargetVideoPlayerHostName[chip::Dnssd::kHostNameMaxLength + 1] = {}; + char mTargetVideoPlayerInstanceName[chip::Dnssd::Commission::kInstanceNameMaxLength + 1] = {}; + size_t mTargetVideoPlayerNumIPs = 0; // number of valid IP addresses chip::Inet::IPAddress mTargetVideoPlayerIpAddress[chip::Dnssd::CommonResolutionData::kMaxIPAddresses]; chip::Controller::CommissionableNodeController mCommissionableNodeController; @@ -571,4 +579,9 @@ class CastingServer : public AppDelegate ChangeChannelCommand mChangeChannelCommand; LineupSubscriber mLineupSubscriber; + + /** + * @brief WakeOnLan cluster + */ + MACAddressReader mMACAddressReader; }; diff --git a/examples/tv-casting-app/tv-casting-common/include/PersistenceManager.h b/examples/tv-casting-app/tv-casting-common/include/PersistenceManager.h index f0774c3d0a2c23..bbbba707289faa 100644 --- a/examples/tv-casting-app/tv-casting-common/include/PersistenceManager.h +++ b/examples/tv-casting-app/tv-casting-common/include/PersistenceManager.h @@ -34,6 +34,8 @@ class PersistenceManager : public chip::FabricTable::Delegate CHIP_ERROR PurgeVideoPlayerCache(); + CHIP_ERROR DeleteVideoPlayer(TargetVideoPlayerInfo * targetVideoPlayerInfo); + private: CHIP_ERROR WriteAllVideoPlayers(TargetVideoPlayerInfo videoPlayers[]); @@ -55,6 +57,10 @@ class PersistenceManager : public chip::FabricTable::Delegate kVideoPlayerNumIPsTag, kVideoPlayerIPAddressTag, kIpAddressesContainerTag, + kVideoPlayerLastDiscoveredTag, + kVideoPlayerMACAddressTag, + kVideoPlayerInstanceNameTag, + kVideoPlayerPortTag, kContextTagMaxNum = UINT8_MAX }; diff --git a/examples/tv-casting-app/tv-casting-common/include/TargetVideoPlayerInfo.h b/examples/tv-casting-app/tv-casting-common/include/TargetVideoPlayerInfo.h index bbc086ae64a52d..a3a453821524d1 100644 --- a/examples/tv-casting-app/tv-casting-common/include/TargetVideoPlayerInfo.h +++ b/examples/tv-casting-app/tv-casting-common/include/TargetVideoPlayerInfo.h @@ -25,6 +25,10 @@ #include +#include +#include +#include + constexpr size_t kMaxNumberOfEndpoints = 5; class TargetVideoPlayerInfo; @@ -84,6 +88,17 @@ class TargetVideoPlayerInfo bool IsSameAs(const chip::Dnssd::DiscoveredNodeData * discoveredNodeData); bool IsSameAs(const char * hostName, const char * deviceName, size_t numIPs, const chip::Inet::IPAddress * ipAddresses); + chip::System::Clock::Timestamp GetLastDiscovered() { return mLastDiscovered; } + uint16_t GetPort() const { return mPort; } + const char * GetInstanceName() const { return mInstanceName; } + chip::CharSpan * GetMACAddress() { return &mMACAddress; } + void SetLastDiscovered(chip::System::Clock::Timestamp lastDiscovered) { mLastDiscovered = lastDiscovered; } + void SetMACAddress(chip::CharSpan MACAddress) + { + memcpy(mMACAddressBuf, MACAddress.data(), MACAddress.size()); + mMACAddress = chip::CharSpan(mMACAddressBuf, MACAddress.size()); + } + chip::OperationalDeviceProxy * GetOperationalDeviceProxy() { if (mDeviceProxy != nullptr && mDeviceProxy->ConnectionReady()) @@ -97,7 +112,9 @@ class TargetVideoPlayerInfo std::function onConnectionSuccess, std::function onConnectionFailure, uint16_t vendorId = 0, uint16_t productId = 0, chip::DeviceTypeId deviceType = 0, const char * deviceName = {}, const char * hostName = {}, - size_t numIPs = 0, chip::Inet::IPAddress * ipAddressList = nullptr); + size_t numIPs = 0, chip::Inet::IPAddress * ipAddressList = nullptr, uint16_t port = 0, + const char * instanceName = {}, + chip::System::Clock::Timestamp lastDiscovered = chip::System::Clock::kZero); CHIP_ERROR FindOrEstablishCASESession(std::function onConnectionSuccess, std::function onConnectionFailure); TargetEndpointInfo * GetOrAddEndpoint(chip::EndpointId endpointId); @@ -173,6 +190,11 @@ class TargetVideoPlayerInfo char mDeviceName[chip::Dnssd::kMaxDeviceNameLen + 1] = {}; char mHostName[chip::Dnssd::kHostNameMaxLength + 1] = {}; size_t mNumIPs = 0; // number of valid IP addresses + char mInstanceName[chip::Dnssd::Commission::kInstanceNameMaxLength + 1]; + uint16_t mPort; chip::Inet::IPAddress mIpAddress[chip::Dnssd::CommonResolutionData::kMaxIPAddresses]; + chip::CharSpan mMACAddress; + char mMACAddressBuf[2 * chip::DeviceLayer::ConfigurationManager::kMaxMACAddressLength]; + chip::System::Clock::Timestamp mLastDiscovered; bool mInitialized = false; }; diff --git a/examples/tv-casting-app/tv-casting-common/include/WakeOnLan.h b/examples/tv-casting-app/tv-casting-common/include/WakeOnLan.h new file mode 100644 index 00000000000000..fda6fab7115955 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/include/WakeOnLan.h @@ -0,0 +1,31 @@ +/* + * + * 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 "MediaReadBase.h" + +#include + +#include + +// READER CLASSES + +class MACAddressReader : public MediaReadBase +{ +public: + MACAddressReader() : MediaReadBase(chip::app::Clusters::WakeOnLan::Id) {} +}; diff --git a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp index 1d40cb500c287e..830b3791e00ae4 100644 --- a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp +++ b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp @@ -25,6 +25,9 @@ using namespace chip::Controller; using namespace chip::Credentials; using namespace chip::app::Clusters::ContentLauncher::Commands; +constexpr int kBroadcastOption = 1; +constexpr int kWoLMagicPacketSize = 102; + CastingServer * CastingServer::castingServer_ = nullptr; CastingServer::CastingServer() {} @@ -217,6 +220,7 @@ chip::Inet::IPAddress * CastingServer::getIpAddressForUDCRequest(chip::Inet::IPA CHIP_ERROR CastingServer::SendUserDirectedCommissioningRequest(Dnssd::DiscoveredNodeData * selectedCommissioner) { + LogCachedVideoPlayers(); mUdcInProgress = true; // Send User Directed commissioning request chip::Inet::IPAddress * ipAddressToUse = @@ -226,6 +230,7 @@ CHIP_ERROR CastingServer::SendUserDirectedCommissioningRequest(Dnssd::Discovered mTargetVideoPlayerVendorId = selectedCommissioner->commissionData.vendorId; mTargetVideoPlayerProductId = selectedCommissioner->commissionData.productId; mTargetVideoPlayerDeviceType = selectedCommissioner->commissionData.deviceType; + mTargetVideoPlayerPort = selectedCommissioner->resolutionData.port; mTargetVideoPlayerNumIPs = selectedCommissioner->resolutionData.numIPs; for (size_t i = 0; i < mTargetVideoPlayerNumIPs && i < chip::Dnssd::CommonResolutionData::kMaxIPAddresses; i++) { @@ -235,25 +240,187 @@ CHIP_ERROR CastingServer::SendUserDirectedCommissioningRequest(Dnssd::Discovered selectedCommissioner->commissionData.deviceName); chip::Platform::CopyString(mTargetVideoPlayerHostName, chip::Dnssd::kHostNameMaxLength + 1, selectedCommissioner->resolutionData.hostName); + chip::Platform::CopyString(mTargetVideoPlayerInstanceName, chip::Dnssd::Commission::kInstanceNameMaxLength + 1, + selectedCommissioner->commissionData.instanceName); return CHIP_NO_ERROR; } #endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT +CHIP_ERROR ConvertToDiscoveredNodeData(TargetVideoPlayerInfo * player, Dnssd::DiscoveredNodeData & outNodeData) +{ + if (player == nullptr) + return CHIP_ERROR_INVALID_ARGUMENT; + + outNodeData.commissionData.vendorId = player->GetVendorId(); + outNodeData.commissionData.productId = static_cast(player->GetProductId()); + outNodeData.commissionData.deviceType = player->GetDeviceType(); + outNodeData.resolutionData.numIPs = player->GetNumIPs(); + + const chip::Inet::IPAddress * ipAddresses = player->GetIpAddresses(); + if (ipAddresses != nullptr) + { + for (size_t i = 0; i < outNodeData.resolutionData.numIPs && i < chip::Dnssd::CommonResolutionData::kMaxIPAddresses; i++) + { + outNodeData.resolutionData.ipAddress[i] = ipAddresses[i]; + } + } + + chip::Platform::CopyString(outNodeData.commissionData.deviceName, chip::Dnssd::kMaxDeviceNameLen + 1, player->GetDeviceName()); + chip::Platform::CopyString(outNodeData.resolutionData.hostName, chip::Dnssd::kHostNameMaxLength + 1, player->GetHostName()); + + return CHIP_NO_ERROR; +} + const Dnssd::DiscoveredNodeData * CastingServer::GetDiscoveredCommissioner(int index, chip::Optional & outAssociatedConnectableVideoPlayer) { - const Dnssd::DiscoveredNodeData * discoveredNodeData = mCommissionableNodeController.GetDiscoveredCommissioner(index); - if (discoveredNodeData != nullptr) + // get actively discovered commissioner at 'index', if any + const Dnssd::DiscoveredNodeData * activelyDiscoveredNodeData = mCommissionableNodeController.GetDiscoveredCommissioner(index); + + // return actively discovered commissioner, if any (+ associate it to a corresponding cached video player) + if (activelyDiscoveredNodeData != nullptr) { for (size_t i = 0; i < kMaxCachedVideoPlayers && mCachedTargetVideoPlayerInfo[i].IsInitialized(); i++) { - if (mCachedTargetVideoPlayerInfo[i].IsSameAs(discoveredNodeData)) + if (mCachedTargetVideoPlayerInfo[i].IsSameAs(activelyDiscoveredNodeData)) { outAssociatedConnectableVideoPlayer = MakeOptional(&mCachedTargetVideoPlayerInfo[i]); + + mCachedTargetVideoPlayerInfo[i].SetLastDiscovered(System::SystemClock().GetMonotonicMilliseconds64()); + CHIP_ERROR err = CastingServer::GetInstance()->mPersistenceManager.AddVideoPlayer(&mCachedTargetVideoPlayerInfo[i]); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "AddVideoPlayer(ToCache) error: %" CHIP_ERROR_FORMAT, err.Format()); + } } } + + return activelyDiscoveredNodeData; + } + else // if no actively discovered commissioner at 'index', return a cached video player that supports STR/WoL + { + chip::Dnssd::DiscoveredNodeData * strNodeData = nullptr; + // count total number of actively discoverable commissioners + int discoveredCommissionerCount; + for (discoveredCommissionerCount = 0; + mCommissionableNodeController.GetDiscoveredCommissioner(discoveredCommissionerCount) != nullptr; + discoveredCommissionerCount++) + ; + + // counter used to determine which video player to return from the list of cached video players + int targetSTRVideoPlayerCounter = index - discoveredCommissionerCount; + if (targetSTRVideoPlayerCounter < 0) // cannot be less than 0 + { + ChipLogError(AppServer, "CastingServer::GetDiscoveredCommissioner encountered invalid targetSTRVideoPlayerCounter %d", + targetSTRVideoPlayerCounter); + return nullptr; + } + strNodeData = &mStrNodeDataList[targetSTRVideoPlayerCounter]; + + // search/loop through all the cached video players + for (size_t i = 0; i < kMaxCachedVideoPlayers && mCachedTargetVideoPlayerInfo[i].IsInitialized(); i++) + { + // search/loop through all actively discovered commissioners so we can skip mCachedTargetVideoPlayerInfo[i] if it is the + // same as an actively discovered commissioner + const Dnssd::DiscoveredNodeData * discoveredNodeData = nullptr; + for (int j = 0; j < CHIP_DEVICE_CONFIG_MAX_DISCOVERED_NODES; j++) + { + discoveredNodeData = mCommissionableNodeController.GetDiscoveredCommissioner(j); + if (discoveredNodeData != nullptr && discoveredNodeData->resolutionData.IsValid()) + { + // consider a cached video player only if it was NOT in the list of actively discoverable commissioners + if (!mCachedTargetVideoPlayerInfo[i].IsSameAs(discoveredNodeData)) + { + // if this cached video player supports STR/WoL and was discoverable recently, convert it to a + // DiscoveredNodeData object and break out of the loop + if (mCachedTargetVideoPlayerInfo[i].GetMACAddress() != nullptr && + mCachedTargetVideoPlayerInfo[i].GetMACAddress()->size() > 0 +#ifdef CHIP_DEVICE_CONFIG_STR_CACHE_LAST_DISCOVERED_DAYS + && mCachedTargetVideoPlayerInfo[i].GetLastDiscovered().count() > + (System::SystemClock().GetMonotonicMilliseconds64().count() - + CHIP_DEVICE_CONFIG_STR_CACHE_LAST_DISCOVERED_DAYS * 24 * 60 * 60 * 1000) +#endif + ) + { + // ensure we are returning the cached video player at the correct index + if (targetSTRVideoPlayerCounter-- == 0) + { + if (ConvertToDiscoveredNodeData(&mCachedTargetVideoPlayerInfo[i], *strNodeData) == CHIP_NO_ERROR) + { + return nullptr; + } + } + break; + } + } + } + } + } + return strNodeData; + } +} + +CHIP_ERROR CastingServer::SendWakeOnLAN(TargetVideoPlayerInfo & targetVideoPlayerInfo) +{ + ChipLogProgress(AppServer, "SendWakeOnLAN called"); + chip::CharSpan * MACAddress = targetVideoPlayerInfo.GetMACAddress(); + VerifyOrReturnError(MACAddress != nullptr && MACAddress->size() > 0, CHIP_ERROR_INVALID_ARGUMENT); + const int kMACLength = chip::DeviceLayer::ConfigurationManager::kPrimaryMACAddressLength; + + // Create a socket + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + + if (sockfd < 0) + { + ChipLogError(AppServer, "socket(): Could not create socket"); + return CHIP_ERROR_INCORRECT_STATE; + } + + // Enable broadcast option + if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &kBroadcastOption, sizeof(kBroadcastOption)) < 0) + { + ChipLogError(AppServer, "setsockopt(): Could not enable broadcast option on socket"); + close(sockfd); + return CHIP_ERROR_INCORRECT_STATE; + } + + // Convert MAC Address to bytes + uint8_t MACBytes[kMACLength]; + for (int i = 0; i < 2 * kMACLength; i += 2) + { + char byteString[3]; + byteString[0] = MACAddress->data()[i]; + byteString[1] = MACAddress->data()[i + 1]; + byteString[2] = '\0'; + MACBytes[i / 2] = static_cast(std::strtol(byteString, nullptr, 16)); + } + + // Create the Wake On LAN "magic" packet + char magicPacket[kWoLMagicPacketSize]; + std::memset(magicPacket, 0xFF, kMACLength); + for (int i = kMACLength; i < kWoLMagicPacketSize; i += kMACLength) + { + std::memcpy(magicPacket + i, MACBytes, kMACLength); + } + + // Set up the broadcast address + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(9); + addr.sin_addr.s_addr = INADDR_BROADCAST; + + // Send the Wake On LAN packet + ssize_t bytesSent = sendto(sockfd, magicPacket, kWoLMagicPacketSize, 0, (struct sockaddr *) &addr, sizeof(addr)); + if (bytesSent < 0) + { + ChipLogError(AppServer, "sendto(): Could not send WoL magic packet"); + return CHIP_ERROR_INCORRECT_STATE; } - return discoveredNodeData; + ChipLogProgress(AppServer, "Broadcasted WoL magic packet with MACAddress %.*s", 2 * kMACLength, MACAddress->data()); + + close(sockfd); + return CHIP_NO_ERROR; } void CastingServer::ReadServerClustersForNode(NodeId nodeId) @@ -336,6 +503,32 @@ void CastingServer::OnDescriptorReadSuccessResponse(void * context, const app::D ChipLogError(AppServer, "AddVideoPlayer(ToCache) error: %" CHIP_ERROR_FORMAT, err.Format()); } + if (endpointInfo->HasCluster(chip::app::Clusters::WakeOnLan::Id)) + { + // Read MAC address + ChipLogProgress(AppServer, "Endpoint supports WoL. Reading VideoPlayer's MACAddress"); + CastingServer::GetInstance()->mMACAddressReader.SetTarget(CastingServer::GetInstance()->mActiveTargetVideoPlayerInfo, + endpointInfo->GetEndpointId()); + CastingServer::GetInstance()->mMACAddressReader.ReadAttribute( + &CastingServer::GetInstance()->mActiveTargetVideoPlayerInfo, + [](void * context, const chip::app::Clusters::WakeOnLan::Attributes::MACAddress::TypeInfo::DecodableArgType response) { + ChipLogProgress(AppServer, "Read MACAddress successfully"); + TargetVideoPlayerInfo * videoPlayerInfo = static_cast(context); + if (response.data() != nullptr && response.size() > 0) + { + videoPlayerInfo->SetMACAddress(response); + ChipLogProgress(AppServer, "Updating cache of VideoPlayers with MACAddress: %.*s", + static_cast(response.size()), response.data()); + CHIP_ERROR err = CastingServer::GetInstance()->mPersistenceManager.AddVideoPlayer(videoPlayerInfo); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "AddVideoPlayer(ToCache) error: %" CHIP_ERROR_FORMAT, err.Format()); + } + } + }, + [](void * context, CHIP_ERROR error) { ChipLogError(AppServer, "Failed to read MACAddress"); }); + } + if (CastingServer::GetInstance()->mOnNewOrUpdatedEndpoint) { CastingServer::GetInstance()->mOnNewOrUpdatedEndpoint(endpointInfo); @@ -365,7 +558,7 @@ TargetVideoPlayerInfo * CastingServer::ReadCachedTargetVideoPlayerInfos() CHIP_ERROR err = mPersistenceManager.ReadAllVideoPlayers(mCachedTargetVideoPlayerInfo); if (err != CHIP_NO_ERROR) { - ChipLogError(AppServer, "ReadAllVideoPlayers error: %" CHIP_ERROR_FORMAT, err.Format()); + ChipLogProgress(AppServer, "ReadAllVideoPlayers error: %" CHIP_ERROR_FORMAT, err.Format()); return nullptr; } return mCachedTargetVideoPlayerInfo; @@ -408,9 +601,55 @@ CHIP_ERROR CastingServer::VerifyOrEstablishConnection(TargetVideoPlayerInfo & ta [](TargetVideoPlayerInfo * videoPlayer) { ChipLogProgress(AppServer, "CastingServer::OnConnectionSuccess lambda called"); CastingServer::GetInstance()->mActiveTargetVideoPlayerInfo = *videoPlayer; - CastingServer::GetInstance()->mOnConnectionSuccessClientCallback(videoPlayer); + + // Read cached VideoPlayer endpoint (1) and check if it supports WoL + TargetEndpointInfo * endpoint = videoPlayer->GetEndpoint(1); + if (endpoint != nullptr && endpoint->HasCluster(chip::app::Clusters::WakeOnLan::Id)) + { + ChipLogProgress(AppServer, "Endpoint supports WoL. Reading VideoPlayer's MACAddress"); + + // Read MAC address + CastingServer::GetInstance()->mMACAddressReader.SetTarget(*videoPlayer, endpoint->GetEndpointId()); + CastingServer::GetInstance()->mMACAddressReader.ReadAttribute( + videoPlayer, + [](void * context, + const chip::app::Clusters::WakeOnLan::Attributes::MACAddress::TypeInfo::DecodableArgType response) { + ChipLogProgress(AppServer, "Read MACAddress successfully"); + TargetVideoPlayerInfo * videoPlayerInfo = static_cast(context); + if (response.data() != nullptr && response.size() > 0) + { + videoPlayerInfo->SetMACAddress(response); + ChipLogProgress(AppServer, "Updating cache of VideoPlayers with MACAddress: %.*s", + static_cast(response.size()), response.data()); + CHIP_ERROR err = CastingServer::GetInstance()->mPersistenceManager.AddVideoPlayer(videoPlayerInfo); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, "AddVideoPlayer(ToCache) error: %" CHIP_ERROR_FORMAT, err.Format()); + } + } + + CastingServer::GetInstance()->mOnConnectionSuccessClientCallback( + static_cast(context)); + }, + [](void * context, CHIP_ERROR error) { + ChipLogError(AppServer, "Failed to read MACAddress"); + CastingServer::GetInstance()->mOnConnectionSuccessClientCallback( + static_cast(context)); + }); + } + else + { + ChipLogProgress(AppServer, "TargetVideoPlayer does not support WoL"); + CastingServer::GetInstance()->mOnConnectionSuccessClientCallback(videoPlayer); + } }, - onConnectionFailure); + [onConnectionFailure](CHIP_ERROR err) { + ChipLogProgress(AppServer, "Deleting VideoPlayer from cache after connection failure: %" CHIP_ERROR_FORMAT, + err.Format()); + CastingServer::GetInstance()->mPersistenceManager.DeleteVideoPlayer( + &CastingServer::GetInstance()->mActiveTargetVideoPlayerInfo); + onConnectionFailure(err); + }); } CHIP_ERROR CastingServer::PurgeCache() @@ -418,6 +657,11 @@ CHIP_ERROR CastingServer::PurgeCache() return mPersistenceManager.PurgeVideoPlayerCache(); } +CHIP_ERROR CastingServer::AddVideoPlayer(TargetVideoPlayerInfo * targetVideoPlayerInfo) +{ + return CastingServer::GetInstance()->mPersistenceManager.AddVideoPlayer(targetVideoPlayerInfo); +} + [[deprecated("Use ContentLauncher_LaunchURL(..) instead")]] CHIP_ERROR CastingServer::ContentLauncherLaunchURL(TargetEndpointInfo * endpoint, const char * contentUrl, const char * contentDisplayStr, std::function launchURLResponseCallback) @@ -506,7 +750,8 @@ void CastingServer::DeviceEventCallback(const DeviceLayer::ChipDeviceEvent * eve CastingServer::GetInstance()->mTargetVideoPlayerVendorId, CastingServer::GetInstance()->mTargetVideoPlayerProductId, CastingServer::GetInstance()->mTargetVideoPlayerDeviceType, CastingServer::GetInstance()->mTargetVideoPlayerDeviceName, CastingServer::GetInstance()->mTargetVideoPlayerHostName, CastingServer::GetInstance()->mTargetVideoPlayerNumIPs, - CastingServer::GetInstance()->mTargetVideoPlayerIpAddress); + CastingServer::GetInstance()->mTargetVideoPlayerIpAddress, CastingServer::GetInstance()->mTargetVideoPlayerPort, + CastingServer::GetInstance()->mTargetVideoPlayerInstanceName, System::SystemClock().GetMonotonicMilliseconds64()); if (err != CHIP_NO_ERROR) { diff --git a/examples/tv-casting-app/tv-casting-common/src/PersistenceManager.cpp b/examples/tv-casting-app/tv-casting-common/src/PersistenceManager.cpp index 32d4cbbfe2fb02..831c756b66b2d8 100644 --- a/examples/tv-casting-app/tv-casting-common/src/PersistenceManager.cpp +++ b/examples/tv-casting-app/tv-casting-common/src/PersistenceManager.cpp @@ -20,6 +20,8 @@ #include #include +#include +#include using namespace chip; @@ -64,6 +66,47 @@ CHIP_ERROR PersistenceManager::AddVideoPlayer(TargetVideoPlayerInfo * targetVide return WriteAllVideoPlayers(cachedVideoPlayers); } +CHIP_ERROR PersistenceManager::DeleteVideoPlayer(TargetVideoPlayerInfo * targetVideoPlayerInfo) +{ + ChipLogProgress(AppServer, "PersistenceManager::DeleteVideoPlayer called"); + VerifyOrReturnError(targetVideoPlayerInfo != nullptr && targetVideoPlayerInfo->IsInitialized(), CHIP_ERROR_INVALID_ARGUMENT); + + // Read cache for video players targetted in previous runs + TargetVideoPlayerInfo cachedVideoPlayers[kMaxCachedVideoPlayers]; + CHIP_ERROR err = ReadAllVideoPlayers(cachedVideoPlayers); + if (err != CHIP_NO_ERROR) + { + ChipLogError(AppServer, + "PersistenceManager::DeleteVideoPlayer status of reading previously cached video players %" CHIP_ERROR_FORMAT, + err.Format()); + return err; + } + + size_t i; + bool found = false; + for (i = 0; i < kMaxCachedVideoPlayers && cachedVideoPlayers[i].IsInitialized(); i++) + { + if (cachedVideoPlayers[i] == *targetVideoPlayerInfo) // found the video player, delete it + { + ChipLogProgress(AppServer, "PersistenceManager::DeleteVideoPlayer found video player in cache at position: %lu", + static_cast(i)); + found = true; + break; + } + } + if (found) + { + while (i + 1 < kMaxCachedVideoPlayers && cachedVideoPlayers[i + 1].IsInitialized()) + { + cachedVideoPlayers[i] = cachedVideoPlayers[i + 1]; + i++; + } + cachedVideoPlayers[i].Reset(); + return WriteAllVideoPlayers(cachedVideoPlayers); + } + return CHIP_NO_ERROR; +} + CHIP_ERROR PersistenceManager::WriteAllVideoPlayers(TargetVideoPlayerInfo videoPlayers[]) { ChipLogProgress(AppServer, "PersistenceManager::WriteAllVideoPlayers called"); @@ -95,12 +138,27 @@ CHIP_ERROR PersistenceManager::WriteAllVideoPlayers(TargetVideoPlayerInfo videoP ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(kVideoPlayerVendorIdTag), videoPlayer->GetVendorId())); ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(kVideoPlayerProductIdTag), videoPlayer->GetProductId())); ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(kVideoPlayerDeviceTypeIdTag), videoPlayer->GetDeviceType())); + ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(kVideoPlayerPortTag), videoPlayer->GetPort())); ReturnErrorOnFailure(tlvWriter.PutBytes(TLV::ContextTag(kVideoPlayerDeviceNameTag), (const uint8_t *) videoPlayer->GetDeviceName(), static_cast(strlen(videoPlayer->GetDeviceName()) + 1))); ReturnErrorOnFailure(tlvWriter.PutBytes(TLV::ContextTag(kVideoPlayerHostNameTag), (const uint8_t *) videoPlayer->GetHostName(), static_cast(strlen(videoPlayer->GetHostName()) + 1))); + ReturnErrorOnFailure(tlvWriter.PutBytes(TLV::ContextTag(kVideoPlayerInstanceNameTag), + (const uint8_t *) videoPlayer->GetInstanceName(), + static_cast(strlen(videoPlayer->GetInstanceName()) + 1))); + ReturnErrorOnFailure( + tlvWriter.Put(TLV::ContextTag(kVideoPlayerLastDiscoveredTag), videoPlayer->GetLastDiscovered().count())); + if (videoPlayer->GetMACAddress() != nullptr && videoPlayer->GetMACAddress()->size() > 0) + { + ChipLogProgress(AppServer, "PersistenceManager::Write MAC Address Length %zu [%d]", + videoPlayer->GetMACAddress()->size(), + chip::DeviceLayer::ConfigurationManager::kMaxMACAddressLength); + ReturnErrorOnFailure(tlvWriter.PutBytes(TLV::ContextTag(kVideoPlayerMACAddressTag), + (const uint8_t *) videoPlayer->GetMACAddress()->data(), + static_cast(videoPlayer->GetMACAddress()->size()))); + } ReturnErrorOnFailure( tlvWriter.Put(TLV::ContextTag(kVideoPlayerNumIPsTag), static_cast(videoPlayer->GetNumIPs()))); @@ -212,6 +270,12 @@ CHIP_ERROR PersistenceManager::ReadAllVideoPlayers(TargetVideoPlayerInfo outVide char hostName[chip::Dnssd::kHostNameMaxLength + 1] = {}; size_t numIPs = 0; Inet::IPAddress ipAddress[chip::Dnssd::CommonResolutionData::kMaxIPAddresses]; + uint64_t lastDiscoveredTicks = 0; + char MACAddressBuf[2 * chip::DeviceLayer::ConfigurationManager::kMaxMACAddressLength] = {}; + uint32_t MACAddressLength = 0; + char instanceName[chip::Dnssd::Commission::kInstanceNameMaxLength + 1] = {}; + uint16_t port = 0; + CHIP_ERROR err; while ((err = reader.Next()) == CHIP_NO_ERROR) { @@ -253,6 +317,12 @@ CHIP_ERROR PersistenceManager::ReadAllVideoPlayers(TargetVideoPlayerInfo outVide continue; } + if (videoPlayersContainerTagNum == kVideoPlayerPortTag) + { + ReturnErrorOnFailure(reader.Get(port)); + continue; + } + if (videoPlayersContainerTagNum == kVideoPlayerDeviceNameTag) { ReturnErrorOnFailure(reader.GetBytes(reinterpret_cast(deviceName), chip::Dnssd::kMaxDeviceNameLen + 1)); @@ -265,6 +335,29 @@ CHIP_ERROR PersistenceManager::ReadAllVideoPlayers(TargetVideoPlayerInfo outVide continue; } + if (videoPlayersContainerTagNum == kVideoPlayerInstanceNameTag) + { + ReturnErrorOnFailure( + reader.GetBytes(reinterpret_cast(instanceName), chip::Dnssd::Commission::kInstanceNameMaxLength + 1)); + continue; + } + + if (videoPlayersContainerTagNum == kVideoPlayerLastDiscoveredTag) + { + ReturnErrorOnFailure(reader.Get(lastDiscoveredTicks)); + continue; + } + + if (videoPlayersContainerTagNum == kVideoPlayerMACAddressTag) + { + MACAddressLength = reader.GetLength(); + ChipLogProgress(AppServer, "PersistenceManager::Read MAC Address Length %u [%d]", MACAddressLength, + chip::DeviceLayer::ConfigurationManager::kMaxMACAddressLength); + ReturnErrorOnFailure(reader.GetBytes(reinterpret_cast(MACAddressBuf), + 2 * chip::DeviceLayer::ConfigurationManager::kMaxMACAddressLength)); + continue; + } + if (videoPlayersContainerTagNum == kVideoPlayerNumIPsTag) { ReturnErrorOnFailure(reader.Get(reinterpret_cast(numIPs))); @@ -312,7 +405,14 @@ CHIP_ERROR PersistenceManager::ReadAllVideoPlayers(TargetVideoPlayerInfo outVide if (videoPlayersContainerTagNum == kContentAppEndpointsContainerTag) { outVideoPlayers[videoPlayerIndex].Initialize(nodeId, fabricIndex, nullptr, nullptr, vendorId, productId, deviceType, - deviceName, hostName, numIPs, ipAddress); + deviceName, hostName, numIPs, ipAddress, port, instanceName, + chip::System::Clock::Timestamp(lastDiscoveredTicks)); + if (MACAddressLength > 0) + { + chip::CharSpan MACAddress(MACAddressBuf, MACAddressLength); + outVideoPlayers[videoPlayerIndex].SetMACAddress(MACAddress); + } + // Entering Content App Endpoints container TLV::TLVType contentAppEndpointArrayContainerType = TLV::kTLVType_Array; ReturnErrorOnFailure(reader.EnterContainer(contentAppEndpointArrayContainerType)); diff --git a/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp b/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp index c075a34c218f91..9039785e7e9293 100644 --- a/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp +++ b/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp @@ -27,16 +27,19 @@ CHIP_ERROR TargetVideoPlayerInfo::Initialize(NodeId nodeId, FabricIndex fabricIn std::function onConnectionSuccess, std::function onConnectionFailure, uint16_t vendorId, uint16_t productId, chip::DeviceTypeId deviceType, const char * deviceName, - const char * hostName, size_t numIPs, chip::Inet::IPAddress * ipAddress) + const char * hostName, size_t numIPs, chip::Inet::IPAddress * ipAddress, uint16_t port, + const char * instanceName, chip::System::Clock::Timestamp lastDiscovered) { ChipLogProgress(NotSpecified, "TargetVideoPlayerInfo nodeId=0x" ChipLogFormatX64 " fabricIndex=%d", ChipLogValueX64(nodeId), fabricIndex); - mNodeId = nodeId; - mFabricIndex = fabricIndex; - mVendorId = vendorId; - mProductId = productId; - mDeviceType = deviceType; - mNumIPs = numIPs; + mNodeId = nodeId; + mFabricIndex = fabricIndex; + mVendorId = vendorId; + mProductId = productId; + mDeviceType = deviceType; + mNumIPs = numIPs; + mPort = port; + mLastDiscovered = lastDiscovered; for (size_t i = 0; i < numIPs && i < chip::Dnssd::CommonResolutionData::kMaxIPAddresses; i++) { mIpAddress[i] = ipAddress[i]; @@ -54,6 +57,12 @@ CHIP_ERROR TargetVideoPlayerInfo::Initialize(NodeId nodeId, FabricIndex fabricIn chip::Platform::CopyString(mHostName, chip::Dnssd::kHostNameMaxLength, hostName); } + memset(mInstanceName, '\0', sizeof(mInstanceName)); + if (instanceName != nullptr) + { + chip::Platform::CopyString(mInstanceName, chip::Dnssd::Commission::kInstanceNameMaxLength + 1, instanceName); + } + for (auto & endpointInfo : mEndpoints) { endpointInfo.Reset(); @@ -71,12 +80,13 @@ CHIP_ERROR TargetVideoPlayerInfo::Initialize(NodeId nodeId, FabricIndex fabricIn void TargetVideoPlayerInfo::Reset() { ChipLogProgress(NotSpecified, "TargetVideoPlayerInfo Reset() called"); - mInitialized = false; - mNodeId = 0; - mFabricIndex = 0; - mVendorId = 0; - mProductId = 0; - mDeviceType = 0; + mInitialized = false; + mNodeId = 0; + mFabricIndex = 0; + mVendorId = 0; + mProductId = 0; + mDeviceType = 0; + mLastDiscovered = chip::System::Clock::kZero; memset(mDeviceName, '\0', sizeof(mDeviceName)); memset(mHostName, '\0', sizeof(mHostName)); mDeviceProxy = nullptr; @@ -154,8 +164,15 @@ bool TargetVideoPlayerInfo::HasEndpoint(EndpointId endpointId) void TargetVideoPlayerInfo::PrintInfo() { - ChipLogProgress(NotSpecified, " TargetVideoPlayerInfo deviceName=%s nodeId=0x" ChipLogFormatX64 " fabric index=%d", mDeviceName, - ChipLogValueX64(mNodeId), mFabricIndex); + ChipLogProgress(NotSpecified, + " TargetVideoPlayerInfo deviceName=%s nodeId=0x" ChipLogFormatX64 " fabric index=%d" + " lastDiscovered=%lu", + mDeviceName, ChipLogValueX64(mNodeId), mFabricIndex, static_cast(mLastDiscovered.count())); + if (mMACAddress.size() > 0) + { + ChipLogProgress(NotSpecified, " MACAddress=%.*s", static_cast(mMACAddress.size()), mMACAddress.data()); + } + for (auto & endpointInfo : mEndpoints) { if (endpointInfo.IsInitialized())