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 6febccf059c5b6..6b43069d3d7ae5 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 @@ -188,6 +188,7 @@ private void reportSleepingCommissioners( && player.getLastDiscoveredMs() > System.currentTimeMillis() - STR_CACHE_LAST_DISCOVERED_DAYS * 24 * 60 * 60 * 1000*/ ) { + player.setAsleep(true); discoverySuccessCallback.handle(new DiscoveredNodeData(player)); } } @@ -233,12 +234,77 @@ public native boolean openBasicCommissioningWindow( public native List readCachedVideoPlayers(); - public native boolean verifyOrEstablishConnection( + public boolean verifyOrEstablishConnection( + VideoPlayer targetVideoPlayer, + SuccessCallback onConnectionSuccess, + FailureCallback onConnectionFailure, + SuccessCallback onNewOrUpdatedEndpointCallback) { + // check if the targetVideoPlayer is asleep and if so, send WakeOnLAN packet to it + if (targetVideoPlayer.isAsleep()) { + boolean status = false; + if (_sendWakeOnLAN(targetVideoPlayer)) { + // check if it woke up by discovering it + discoverVideoPlayerCommissioners( + new SuccessCallback() { + @Override + public void handle(DiscoveredNodeData response) { + Log.d( + TAG, + "Video player discovered after WakeOnLAN with hostname " + + response.getHostName()); + if (targetVideoPlayer.getHostName().equals(response.getHostName())) { + targetVideoPlayer.setAsleep(false); // not asleep anymore + boolean callStatus = + _verifyOrEstablishConnection( + targetVideoPlayer, + onConnectionSuccess, + onConnectionFailure, + onNewOrUpdatedEndpointCallback); + if (callStatus == false) { + Log.e( + TAG, + "_verifyOrEstablishConnection failed after waking up and discovering targetVideoPlayer"); + onConnectionFailure.handle(new MatterError(0x03, "CHIP_ERROR_INCORRECT_STATE")); + } + } + } + }, + new FailureCallback() { + @Override + public void handle(MatterError err) { + Log.e(TAG, "Failure while discovering targetVideoPlayer after waking it up " + err); + } + }); + + // stop looking for the video player after some time and fail fast + Executors.newScheduledThreadPool(1) + .schedule( + () -> { + Log.d(TAG, "Scheduling stopVideoPlayerDiscovery after sending WoL"); + stopVideoPlayerDiscovery(); + }, + 10000, + TimeUnit.MILLISECONDS); + status = true; + } + return status; + } else { + return _verifyOrEstablishConnection( + targetVideoPlayer, + onConnectionSuccess, + onConnectionFailure, + onNewOrUpdatedEndpointCallback); + } + } + + 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 81f7d0101a54ad..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 @@ -36,6 +36,7 @@ public class VideoPlayer { private List contentApps; private long lastDiscoveredMs; private String MACAddress; + private boolean isAsleep = false; private boolean isConnected = false; private int numIPs; @@ -138,6 +139,9 @@ public String toString() { + ", deviceName='" + deviceName + '\'' + + ", instanceName='" + + instanceName + + '\'' + ", vendorId=" + vendorId + ", productId=" @@ -151,6 +155,8 @@ public String toString() { + ", MACAddress='" + MACAddress + '\'' + + ", isAsleep=" + + isAsleep + ", isConnected=" + isConnected + ", numIPs=" @@ -227,6 +233,14 @@ 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/TvCastingApp-JNI.cpp b/examples/tv-casting-app/android/App/app/src/main/jni/cpp/TvCastingApp-JNI.cpp index 5293cc31305fd5..9f1397f9260c8d 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,7 +243,7 @@ JNI_METHOD(jobject, readCachedVideoPlayers)(JNIEnv * env, jobject) return jVideoPlayerList; } -JNI_METHOD(jboolean, verifyOrEstablishConnection) +JNI_METHOD(jboolean, _verifyOrEstablishConnection) (JNIEnv * env, jobject, jobject videoPlayer, jobject jOnConnectionSuccessHandler, jobject jOnConnectionFailureHandler, jobject jOnNewOrUpdatedEndpointHandler) { @@ -282,6 +282,28 @@ JNI_METHOD(jboolean, verifyOrEstablishConnection) return (err == CHIP_NO_ERROR); } +JNI_METHOD(jboolean, _sendWakeOnLAN) +(JNIEnv * env, jobject, jobject videoPlayer) +{ + chip::DeviceLayer::StackLock lock; + + ChipLogProgress(AppServer, "JNI_METHOD _sendWakeOnLAN 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); +} + JNI_METHOD(void, shutdownAllSubscriptions)(JNIEnv * env, jobject) { chip::DeviceLayer::StackLock lock; 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 4a38fc9557a4a3..7b71d83aa5698d 100644 --- a/examples/tv-casting-app/tv-casting-common/include/CastingServer.h +++ b/examples/tv-casting-app/tv-casting-common/include/CastingServer.h @@ -73,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, 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 be6df6b1f9e7d7..ecff7a9c9893ed 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() {} @@ -357,6 +360,69 @@ CastingServer::GetDiscoveredCommissioner(int index, chip::Optionalsize() > 0, + CHIP_ERROR_INVALID_ARGUMENT); + chip::CharSpan MACAddress = *(targetVideoPlayerInfo.getMACAddress()); + ChipLogProgress(AppServer, "SendWakeOnLAN called with MACAddress %.*s", 2 * kMACLength, MACAddress.data()); + + // 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 + const int kMACLength = chip::DeviceLayer::ConfigurationManager::kPrimaryMACAddressLength; + 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; + } + + close(sockfd); + return CHIP_NO_ERROR; +} + void CastingServer::ReadServerClustersForNode(NodeId nodeId) { ChipLogProgress(NotSpecified, "ReadServerClustersForNode nodeId=0x" ChipLogFormatX64, ChipLogValueX64(nodeId)); 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 801aa38663018d..9039785e7e9293 100644 --- a/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp +++ b/examples/tv-casting-app/tv-casting-common/src/TargetVideoPlayerInfo.cpp @@ -167,7 +167,7 @@ void TargetVideoPlayerInfo::PrintInfo() ChipLogProgress(NotSpecified, " TargetVideoPlayerInfo deviceName=%s nodeId=0x" ChipLogFormatX64 " fabric index=%d" " lastDiscovered=%lu", - mDeviceName, ChipLogValueX64(mNodeId), mFabricIndex, mLastDiscovered.count()); + mDeviceName, ChipLogValueX64(mNodeId), mFabricIndex, static_cast(mLastDiscovered.count())); if (mMACAddress.size() > 0) { ChipLogProgress(NotSpecified, " MACAddress=%.*s", static_cast(mMACAddress.size()), mMACAddress.data());