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..a66d48a57c0e2a 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,19 @@ 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.numIPs = player.getNumIPs(); + 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..8a4af383f383b0 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 @@ -29,8 +29,13 @@ import chip.platform.NsdManagerServiceBrowser; import chip.platform.NsdManagerServiceResolver; import chip.platform.PreferencesKeyValueStoreManager; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; public class TvCastingApp { private static final String TAG = TvCastingApp.class.getSimpleName(); @@ -38,6 +43,9 @@ public class TvCastingApp { private static final List DISCOVERY_TARGET_DEVICE_TYPE_FILTER = Arrays.asList(35L); // Video player = 35; + // delay before which we assume undiscovered cached players may be in STR mode + private static final long CHIP_DEVICE_CONFIG_STR_DISCOVERY_DELAY_SEC = 5; + private static TvCastingApp sInstance; private Context applicationContext; private ChipAppServer chipAppServer; @@ -45,6 +53,8 @@ public class TvCastingApp { private boolean discoveryStarted = false; private Object discoveryLock = new Object(); + private ScheduledFuture reportSleepingVideoPlayerCommissionersFuture; + private WifiManager.MulticastLock multicastLock; private NsdManager nsdManager; private NsdDiscoveryListener nsdDiscoveryListener; @@ -136,30 +146,119 @@ public void discoverVideoPlayerCommissioners( multicastLock.acquire(); nsdManager = (NsdManager) applicationContext.getSystemService(Context.NSD_SERVICE); + List discoveredPlayers = new ArrayList<>(); nsdDiscoveryListener = new NsdDiscoveryListener( nsdManager, DISCOVERY_TARGET_SERVICE_TYPE, DISCOVERY_TARGET_DEVICE_TYPE_FILTER, preCommissionedVideoPlayers, - discoverySuccessCallback, + new SuccessCallback() { + @Override + public void handle(DiscoveredNodeData commissioner) { + Log.d(TAG, "Discovered commissioner added " + commissioner); + discoveredPlayers.add(commissioner); + discoverySuccessCallback.handle(commissioner); + } + }, discoveryFailureCallback, nsdManagerResolverAvailState); nsdManager.discoverServices( DISCOVERY_TARGET_SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, nsdDiscoveryListener); Log.d(TAG, "TvCastingApp.discoverVideoPlayerCommissioners started"); + + this.reportSleepingVideoPlayerCommissionersFuture = + Executors.newScheduledThreadPool(1) + .schedule( + () -> { + Log.d( + TAG, + "Scheduling reportSleepingCommissioners with commissioner count " + + (preCommissionedVideoPlayers != null + ? preCommissionedVideoPlayers.size() + : 0)); + reportSleepingVideoPlayerCommissioners( + preCommissionedVideoPlayers, discoveredPlayers, discoverySuccessCallback); + }, + CHIP_DEVICE_CONFIG_STR_DISCOVERY_DELAY_SEC * 1000, + TimeUnit.MILLISECONDS); + this.discoveryStarted = true; } } + private void reportSleepingVideoPlayerCommissioners( + List cachedVideoPlayers, + List discoveredPlayers, + SuccessCallback discoverySuccessCallback) { + Log.d( + TAG, + "TvCastingApp.reportSleepingVideoPlayerCommissioners called with commissioner count " + + (cachedVideoPlayers != null ? cachedVideoPlayers.size() : 0)); + if (cachedVideoPlayers != null) { + for (VideoPlayer player : cachedVideoPlayers) { + Log.d(TAG, "Cached Video Player: " + player); + // do NOT surface this cached Player if we don't have its MACAddress + if (player.getMACAddress() == null) { + Log.d( + TAG, + "TvCastingApp.reportSleepingVideoPlayerCommissioners Skipping Player with hostName" + + player.getHostName() + + " but no MACAddress"); + continue; + } + + // do NOT surface this cached Player if it has not been discoverable recently (in + // CHIP_DEVICE_CONFIG_STR_CACHE_LAST_DISCOVERED_HOURS) + if (!WasRecentlyDiscoverable(player)) { + Log.d( + TAG, + "TvCastingApp.reportSleepingVideoPlayerCommissioners Skipping Player with hostName" + + player.getHostName() + + " that has not been discovered recently"); + continue; + } + + // do NOT surface this cached Player if it was just discovered right now (in this discovery + // call) + boolean justDiscovered = + discoveredPlayers + .stream() + .anyMatch( + new Predicate() { + @Override + public boolean test(DiscoveredNodeData discoveredNodeData) { + return player.getHostName().equals(discoveredNodeData.getHostName()); + } + }); + if (justDiscovered) { + Log.d( + TAG, + "TvCastingApp.reportSleepingVideoPlayerCommissioners Skipping Player with hostName" + + player.getHostName() + + " that was just discovered"); + continue; + } + + // DO surface this cached Player (as asleep) + Log.d(TAG, "Reporting sleeping player with hostName " + player.getHostName()); + player.setIsAsleep(true); + discoverySuccessCallback.handle(new DiscoveredNodeData(player)); + } + } + } + + private native boolean WasRecentlyDiscoverable(VideoPlayer 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 + && reportSleepingVideoPlayerCommissionersFuture != null) { Log.d(TAG, "TvCastingApp stopping Video Player commissioner discovery"); try { nsdManager.stopServiceDiscovery(nsdDiscoveryListener); @@ -173,6 +272,8 @@ public void stopVideoPlayerDiscovery() { if (multicastLock.isHeld()) { multicastLock.release(); } + + reportSleepingVideoPlayerCommissionersFuture.cancel(false); this.discoveryStarted = false; } } 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..302e24ccf389ce 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 @@ -38,6 +38,11 @@ public class VideoPlayer { private int numIPs; private List ipAddresses; private String hostName; + private String instanceName; + private long lastDiscoveredMs; + private String MACAddress; + private boolean isAsleep = false; + private int port; private boolean isInitialized = false; @@ -52,6 +57,11 @@ public VideoPlayer( int numIPs, List ipAddresses, String hostName, + String instanceName, + int port, + long lastDiscoveredMs, + String MACAddress, + boolean isAsleep, boolean isConnected) { this.nodeId = nodeId; this.fabricIndex = fabricIndex; @@ -64,6 +74,11 @@ public VideoPlayer( this.numIPs = numIPs; this.ipAddresses = ipAddresses; this.hostName = hostName; + this.MACAddress = MACAddress; + this.lastDiscoveredMs = lastDiscoveredMs; + this.instanceName = instanceName; + this.port = port; + this.isAsleep = isAsleep; this.isInitialized = true; } @@ -116,8 +131,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 @@ -140,10 +155,22 @@ public java.lang.String toString() { + numIPs + ", ipAddresses=" + ipAddresses - + ", isInitialized=" + ", hostName='" + hostName + '\'' + + ", instanceName='" + + instanceName + + '\'' + + ", lastDiscoveredMs=" + + lastDiscoveredMs + + ", MACAddress='" + + MACAddress + + '\'' + + ", isAsleep=" + + isAsleep + + ", port=" + + port + + ", isInitialized=" + isInitialized + '}'; } @@ -180,6 +207,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 setIsAsleep(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..e05937c2812c6b 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) { @@ -155,8 +157,33 @@ CHIP_ERROR convertJVideoPlayerToTargetVideoPlayerInfo(jobject videoPlayer, Targe jstring jHostName = static_cast(env->GetObjectField(videoPlayer, getHostNameField)); const char * hostName = env->GetStringUTFChars(jHostName, 0); + jfieldID jPort = env->GetFieldID(jVideoPlayerClass, "port", "I"); + uint16_t port = static_cast(env->GetIntField(videoPlayer, jPort)); + + 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); + + jfieldID jIsAsleep = env->GetFieldID(jVideoPlayerClass, "isAsleep", "Z"); + bool isAsleep = static_cast(env->GetLongField(videoPlayer, jIsAsleep)); + 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); + } + + outTargetVideoPlayerInfo.SetIsAsleep(isAsleep); jfieldID jContentAppsField = env->GetFieldID(jVideoPlayerClass, "contentApps", "Ljava/util/List;"); jobject jContentApps = env->GetObjectField(videoPlayer, jContentAppsField); @@ -197,7 +224,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 +246,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) @@ -240,11 +282,12 @@ 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); + outVideoPlayer = env->NewObject( + jVideoPlayerClass, jVideoPlayerConstructor, targetVideoPlayerInfo->GetNodeId(), targetVideoPlayerInfo->GetFabricIndex(), + deviceName, targetVideoPlayerInfo->GetVendorId(), targetVideoPlayerInfo->GetProductId(), + targetVideoPlayerInfo->GetDeviceType(), jContentAppList, targetVideoPlayerInfo->GetNumIPs(), jIPAddressList, hostName, + instanceName, targetVideoPlayerInfo->GetPort(), targetVideoPlayerInfo->GetLastDiscovered().count(), MACAddress, + targetVideoPlayerInfo->IsAsleep(), 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..f3a85c606c1576 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,12 +276,31 @@ 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, WasRecentlyDiscoverable) +(JNIEnv * env, jobject, jobject videoPlayer) +{ + chip::DeviceLayer::StackLock lock; + + ChipLogProgress(AppServer, "JNI_METHOD WasRecentlyDiscoverable 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())); + return targetVideoPlayerInfo.WasRecentlyDiscoverable(); + +exit: + return false; // default to false +} + JNI_METHOD(void, shutdownAllSubscriptions)(JNIEnv * env, jobject) { chip::DeviceLayer::StackLock lock; diff --git a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CommissionerDiscoveryDelegateImpl.h b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/CommissionerDiscoveryDelegateImpl.h index 3d54b9acc022c6..282d6d6a97eb0f 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]; @@ -57,6 +74,63 @@ class CommissionerDiscoveryDelegateImpl : public chip::Controller::DeviceDiscove } private: + 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++) { + // do NOT surface this cached Player if we don't have its MACAddress + if (thiz->mCachedTargetVideoPlayerInfos[i].GetMACAddress() == nullptr + && thiz->mCachedTargetVideoPlayerInfos[i].GetMACAddress()->size() == 0) { + ChipLogProgress(NotSpecified, + "CommissionerDiscoveryDelegateImpl().ReportSleepingCommissioners() Skipping Player with hostName %s but no " + "MACAddress", + thiz->mCachedTargetVideoPlayerInfos[i].GetHostName()); + continue; + } + + // do NOT surface this cached Player if it has not been discoverable recently + if (!thiz->mCachedTargetVideoPlayerInfos[i].WasRecentlyDiscoverable()) { + ChipLogProgress(NotSpecified, + "CommissionerDiscoveryDelegateImpl().ReportSleepingCommissioners() Skipping Player with hostName %s that " + "has not been discovered recently", + thiz->mCachedTargetVideoPlayerInfos[i].GetHostName()); + continue; + } + + // do NOT surface this cached Player if it was just discovered right now (in this discovery call) + bool justDiscovered = false; + for (DiscoveredNodeData * discoveredCommissioner : thiz->mDiscoveredCommissioners) { + if (strcmp((char *) [discoveredCommissioner.hostName UTF8String], + thiz->mCachedTargetVideoPlayerInfos[i].GetHostName()) + == 0) { + justDiscovered = true; + break; + } + } + if (justDiscovered) { + ChipLogProgress(NotSpecified, + "CommissionerDiscoveryDelegateImpl().ReportSleepingCommissioners() Skipping Player with hostName %s that " + "was just discovered", + thiz->mCachedTargetVideoPlayerInfos[i].GetHostName()); + continue; + } + + // DO surface this cached Player (as asleep) + DiscoveredNodeData * objCDiscoveredNodeData = + [ConversionUtils convertToDiscoveredNodeDataFrom:&thiz->mCachedTargetVideoPlayerInfos[i]]; + objCDiscoveredNodeData.getConnectableVideoPlayer.isAsleep = true; + ChipLogProgress(AppServer, + "CommissionerDiscoveryDelegateImpl().ReportSleepingCommissioners() reporting sleeping video player with " + "hostName %s", + thiz->mCachedTargetVideoPlayerInfos[i].GetHostName()); + thiz->mObjCDiscoveredCommissionerHandler(objCDiscoveredNodeData); + } + } + } + + std::vector mDiscoveredCommissioners; void (^_Nonnull mObjCDiscoveredCommissionerHandler)(DiscoveredNodeData * _Nonnull); dispatch_queue_t _Nonnull mClientQueue; TargetVideoPlayerInfo * _Nullable mCachedTargetVideoPlayerInfos; 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..a13f231b09b6c0 100644 --- a/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.mm +++ b/examples/tv-casting-app/darwin/MatterTvCastingBridge/MatterTvCastingBridge/ConversionUtils.mm @@ -84,7 +84,15 @@ + (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)); + } + outTargetVideoPlayerInfo.SetIsAsleep(objCVideoPlayer.isAsleep); + for (ContentApp * contentApp in objCVideoPlayer.contentApps) { TargetEndpointInfo * endpoint = outTargetVideoPlayerInfo.GetOrAddEndpoint(contentApp.endpointId); VerifyOrReturnError(endpoint != nullptr, CHIP_ERROR_INCORRECT_STATE); @@ -143,6 +151,36 @@ + (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]; + + 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,18 @@ + (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.isAsleep = cppTargetVideoPlayerInfo->IsAsleep(); + 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/linux/CastingUtils.cpp b/examples/tv-casting-app/linux/CastingUtils.cpp index dca65f54d741b3..c3c2df893fa3fa 100644 --- a/examples/tv-casting-app/linux/CastingUtils.cpp +++ b/examples/tv-casting-app/linux/CastingUtils.cpp @@ -100,7 +100,8 @@ void InitCommissioningFlow(intptr_t commandArg) CastingServer::GetInstance()->GetDiscoveredCommissioner(i, associatedConnectableVideoPlayer); if (commissioner != nullptr) { - ChipLogProgress(AppServer, "Discovered Commissioner #%d", commissionerCount++); + ChipLogProgress(AppServer, "Discovered Commissioner #%d", commissionerCount); + commissionerCount++; commissioner->LogDetail(); if (associatedConnectableVideoPlayer.HasValue()) { diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index 115a0b7e4bbfb0..21c04d63691d2d 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -58,6 +58,7 @@ chip_data_model("tv-casting-common") { "include/CastingServer.h", "include/Channel.h", "include/ContentLauncher.h", + "include/ConversionUtils.h", "include/KeypadInput.h", "include/LevelControl.h", "include/MediaBase.h", @@ -70,11 +71,13 @@ 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", "src/Channel.cpp", "src/ContentLauncher.cpp", + "src/ConversionUtils.cpp", "src/KeypadInput.cpp", "src/LevelControl.cpp", "src/MediaPlayback.cpp", @@ -83,6 +86,7 @@ chip_data_model("tv-casting-common") { "src/TargetEndpointInfo.cpp", "src/TargetNavigator.cpp", "src/TargetVideoPlayerInfo.cpp", + "src/WakeOnLan.cpp", ] # Add simplified casting API files here 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..7756933a998f22 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,15 @@ #define CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT 4 +// cached players that were seen before this window (in hours) will not be surfaced as "discovered" +#define CHIP_DEVICE_CONFIG_STR_CACHE_LAST_DISCOVERED_HOURS 7 * 24L + +// time (in sec) assumed to be required for player to wake up after sending WoL magic packet +#define CHIP_DEVICE_CONFIG_STR_WAKE_UP_DELAY_SEC 10 + +// delay (in sec) before which we assume undiscovered cached players may be in STR mode +#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..56995ca61566d8 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 @@ -100,6 +101,10 @@ class CastingServer : public AppDelegate std::function onNewOrUpdatedEndpoint); void LogCachedVideoPlayers(); + CHIP_ERROR AddVideoPlayer(TargetVideoPlayerInfo * targetVideoPlayerInfo); + + CHIP_ERROR SendWakeOnLan(TargetVideoPlayerInfo & targetVideoPlayerInfo); + CHIP_ERROR PurgeCache(); /** @@ -439,6 +444,8 @@ class CastingServer : public AppDelegate void OnCommissioningWindowOpened() override {} void OnCommissioningWindowClosed() override {} + CHIP_ERROR ReadMACAddress(TargetEndpointInfo * endpoint); + /** * @brief Retrieve the IP Address to use for the UDC request. * This function will look for an IPv4 address in the list of IPAddresses passed in if available and return @@ -455,14 +462,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 +581,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/ConversionUtils.h b/examples/tv-casting-app/tv-casting-common/include/ConversionUtils.h new file mode 100644 index 00000000000000..76f7077d8604f7 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/include/ConversionUtils.h @@ -0,0 +1,23 @@ +/* + * + * 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 "TargetVideoPlayerInfo.h" + +#include + +CHIP_ERROR ConvertToDiscoveredNodeData(TargetVideoPlayerInfo * inPlayer, chip::Dnssd::DiscoveredNodeData & outNodeData); 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..abca61eb1d1024 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,26 @@ 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); + uint16_t GetPort() const { return mPort; } + const char * GetInstanceName() const { return mInstanceName; } + chip::CharSpan * GetMACAddress() { return &mMACAddress; } + void SetIsAsleep(bool isAsleep) { mIsAsleep = isAsleep; } + bool IsAsleep() { return mIsAsleep; } + void SetMACAddress(chip::CharSpan MACAddress) + { + memcpy(mMACAddressBuf, MACAddress.data(), MACAddress.size()); + mMACAddress = chip::CharSpan(mMACAddressBuf, MACAddress.size()); + } + chip::System::Clock::Timestamp GetLastDiscovered() { return mLastDiscovered; } + void SetLastDiscovered(chip::System::Clock::Timestamp lastDiscovered) { mLastDiscovered = lastDiscovered; } + bool WasRecentlyDiscoverable() + { + // it was recently discoverable if its mLastDiscovered.count is within + // CHIP_DEVICE_CONFIG_STR_CACHE_LAST_DISCOVERED_HOURS of current time + return mLastDiscovered.count() > chip::System::SystemClock().GetMonotonicMilliseconds64().count() - + CHIP_DEVICE_CONFIG_STR_CACHE_LAST_DISCOVERED_HOURS * 60 * 60 * 1000; + } + chip::OperationalDeviceProxy * GetOperationalDeviceProxy() { if (mDeviceProxy != nullptr && mDeviceProxy->ConnectionReady()) @@ -97,7 +121,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); @@ -174,5 +200,11 @@ class TargetVideoPlayerInfo char mHostName[chip::Dnssd::kHostNameMaxLength + 1] = {}; size_t mNumIPs = 0; // number of valid IP addresses chip::Inet::IPAddress mIpAddress[chip::Dnssd::CommonResolutionData::kMaxIPAddresses]; + char mInstanceName[chip::Dnssd::Commission::kInstanceNameMaxLength + 1]; + uint16_t mPort; + chip::CharSpan mMACAddress; + char mMACAddressBuf[2 * chip::DeviceLayer::ConfigurationManager::kMaxMACAddressLength]; + chip::System::Clock::Timestamp mLastDiscovered; + bool mIsAsleep = false; 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..7d956bed4b32ed --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/include/WakeOnLan.h @@ -0,0 +1,33 @@ +/* + * + * 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) {} +}; + +CHIP_ERROR SendWakeOnLanPacket(chip::CharSpan * MACAddress); 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..9dd062443658de 100644 --- a/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp +++ b/examples/tv-casting-app/tv-casting-common/src/CastingServer.cpp @@ -17,6 +17,7 @@ */ #include "CastingServer.h" +#include "ConversionUtils.h" #include "app/clusters/bindings/BindingManager.h" @@ -235,6 +236,9 @@ 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); + mTargetVideoPlayerPort = selectedCommissioner->resolutionData.port; return CHIP_NO_ERROR; } #endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY_CLIENT @@ -314,6 +318,45 @@ void CastingServer::ReadServerClusters(EndpointId endpointId) ChipLogProgress(Controller, "Sent descriptor read for remote endpoint=%d", endpointId); } +CHIP_ERROR CastingServer::SendWakeOnLan(TargetVideoPlayerInfo & targetVideoPlayerInfo) +{ + return SendWakeOnLanPacket(targetVideoPlayerInfo.GetMACAddress()); +} + +CHIP_ERROR CastingServer::ReadMACAddress(TargetEndpointInfo * endpoint) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + if (endpoint != nullptr && endpoint->HasCluster(chip::app::Clusters::WakeOnLan::Id)) + { + // Read MAC address + ChipLogProgress(AppServer, "Endpoint supports WoL. Reading Active VideoPlayer's MACAddress"); + mMACAddressReader.SetTarget(mActiveTargetVideoPlayerInfo, endpoint->GetEndpointId()); + err = mMACAddressReader.ReadAttribute( + &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"); }); + } + else + { + err = CHIP_ERROR_INVALID_ARGUMENT; + } + return err; +} + void CastingServer::OnDescriptorReadSuccessResponse(void * context, const app::DataModel::DecodableList & responseList) { TargetEndpointInfo * endpointInfo = static_cast(context); @@ -336,6 +379,9 @@ void CastingServer::OnDescriptorReadSuccessResponse(void * context, const app::D ChipLogError(AppServer, "AddVideoPlayer(ToCache) error: %" CHIP_ERROR_FORMAT, err.Format()); } + // Read WoL:MACAddress (if available from this endpoint) + CastingServer::GetInstance()->ReadMACAddress(endpointInfo); + if (CastingServer::GetInstance()->mOnNewOrUpdatedEndpoint) { CastingServer::GetInstance()->mOnNewOrUpdatedEndpoint(endpointInfo); @@ -365,7 +411,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; @@ -403,14 +449,29 @@ CHIP_ERROR CastingServer::VerifyOrEstablishConnection(TargetVideoPlayerInfo & ta prevDeviceProxy->Disconnect(); } + if (targetVideoPlayerInfo.IsAsleep()) + { + ChipLogProgress(AppServer, "CastingServer::VerifyOrEstablishConnection(): Sending WoL to sleeping VideoPlayer and waiting"); + ReturnErrorOnFailure(CastingServer::GetInstance()->SendWakeOnLan(targetVideoPlayerInfo)); + usleep(CHIP_DEVICE_CONFIG_STR_WAKE_UP_DELAY_SEC * 1000 * 1000); + } + CastingServer::GetInstance()->mActiveTargetVideoPlayerInfo = targetVideoPlayerInfo; return CastingServer::GetInstance()->mActiveTargetVideoPlayerInfo.FindOrEstablishCASESession( [](TargetVideoPlayerInfo * videoPlayer) { ChipLogProgress(AppServer, "CastingServer::OnConnectionSuccess lambda called"); CastingServer::GetInstance()->mActiveTargetVideoPlayerInfo = *videoPlayer; + CastingServer::GetInstance()->ReadMACAddress( + videoPlayer->GetEndpoint(1)); // Read MACAddress from cached VideoPlayer endpoint (1) which supports 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 +479,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 +572,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/ConversionUtils.cpp b/examples/tv-casting-app/tv-casting-common/src/ConversionUtils.cpp new file mode 100644 index 00000000000000..322e9d46bec8c4 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/src/ConversionUtils.cpp @@ -0,0 +1,45 @@ +/* + * + * 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 "ConversionUtils.h" + +CHIP_ERROR ConvertToDiscoveredNodeData(TargetVideoPlayerInfo * inPlayer, chip::Dnssd::DiscoveredNodeData & outNodeData) +{ + if (inPlayer == nullptr) + return CHIP_ERROR_INVALID_ARGUMENT; + + outNodeData.commissionData.vendorId = inPlayer->GetVendorId(); + outNodeData.commissionData.productId = static_cast(inPlayer->GetProductId()); + outNodeData.commissionData.deviceType = inPlayer->GetDeviceType(); + outNodeData.resolutionData.numIPs = inPlayer->GetNumIPs(); + + const chip::Inet::IPAddress * ipAddresses = inPlayer->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, + inPlayer->GetDeviceName()); + chip::Platform::CopyString(outNodeData.resolutionData.hostName, chip::Dnssd::kHostNameMaxLength + 1, inPlayer->GetHostName()); + + return 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..f67cc1334ba036 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"); @@ -101,6 +144,21 @@ CHIP_ERROR PersistenceManager::WriteAllVideoPlayers(TargetVideoPlayerInfo videoP 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(kVideoPlayerPortTag), videoPlayer->GetPort())); + 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 lastDiscoveredMs = 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) { @@ -265,6 +329,35 @@ CHIP_ERROR PersistenceManager::ReadAllVideoPlayers(TargetVideoPlayerInfo outVide continue; } + if (videoPlayersContainerTagNum == kVideoPlayerInstanceNameTag) + { + ReturnErrorOnFailure( + reader.GetBytes(reinterpret_cast(instanceName), chip::Dnssd::Commission::kInstanceNameMaxLength + 1)); + continue; + } + + if (videoPlayersContainerTagNum == kVideoPlayerPortTag) + { + ReturnErrorOnFailure(reader.Get(port)); + continue; + } + + if (videoPlayersContainerTagNum == kVideoPlayerLastDiscoveredTag) + { + ReturnErrorOnFailure(reader.Get(lastDiscoveredMs)); + 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(lastDiscoveredMs)); + 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..7d9c7d51168eaf 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; @@ -156,6 +166,11 @@ void TargetVideoPlayerInfo::PrintInfo() { ChipLogProgress(NotSpecified, " TargetVideoPlayerInfo deviceName=%s nodeId=0x" ChipLogFormatX64 " fabric index=%d", mDeviceName, ChipLogValueX64(mNodeId), mFabricIndex); + if (mMACAddress.size() > 0) + { + ChipLogProgress(NotSpecified, " MACAddress=%.*s", static_cast(mMACAddress.size()), mMACAddress.data()); + } + for (auto & endpointInfo : mEndpoints) { if (endpointInfo.IsInitialized()) diff --git a/examples/tv-casting-app/tv-casting-common/src/WakeOnLan.cpp b/examples/tv-casting-app/tv-casting-common/src/WakeOnLan.cpp new file mode 100644 index 00000000000000..cf44c413a70c03 --- /dev/null +++ b/examples/tv-casting-app/tv-casting-common/src/WakeOnLan.cpp @@ -0,0 +1,84 @@ +/* + * + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "WakeOnLan.h" + +constexpr int kBroadcastOption = 1; +constexpr int kWoLMagicPacketSize = 102; + +CHIP_ERROR SendWakeOnLanPacket(chip::CharSpan * MACAddress) +{ + ChipLogProgress(AppServer, "SendWakeOnLanPacket called"); + 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; + } + ChipLogProgress(AppServer, "Broadcasted WoL magic packet with MACAddress %.*s", 2 * kMACLength, MACAddress->data()); + + close(sockfd); + return CHIP_NO_ERROR; +}