package com.milesmarine.reeflightcontroller import android.content.Intent import android.os.Bundle import android.util.Log import android.view.View import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.json.JSONObject import java.net.DatagramPacket import java.net.DatagramSocket import java.net.HttpURLConnection import java.net.InetAddress import java.net.URL class SecondActivity : AppCompatActivity() { private lateinit var recyclerView: RecyclerView private lateinit var adapter: DeviceAdapter private val deviceList = mutableListOf() // UI Elements for the loading screen private lateinit var loadingLayout: LinearLayout private lateinit var loaderText: TextView // Swipe-to-refresh layout private lateinit var swipeRefreshLayout: SwipeRefreshLayout // SSDP Multicast Address and Port private val SSDP_ADDRESS = "239.255.255.250" private val SSDP_PORT = 1900 private val SSDP_MESSAGE = """ M-SEARCH * HTTP/1.1 HOST: $SSDP_ADDRESS:$SSDP_PORT MAN: "ssdp:discover" MX: 3 ST: ssdp:all """.trimIndent() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_second) // Initialize RecyclerView recyclerView = findViewById(R.id.recyclerViewDevices) recyclerView.layoutManager = LinearLayoutManager(this) val onDeviceClick: (OpenBekenDevice) -> Unit = { device -> // Save the selected device's IP to SharedPreferences val sharedPreferences = getSharedPreferences("DevicePrefs", MODE_PRIVATE) val editor = sharedPreferences.edit() editor.putString("selectedDeviceIP", device.ip) editor.apply() // Send selected device IP back to MainActivity val resultIntent = Intent() resultIntent.putExtra("selectedDeviceIP", device.ip) setResult(RESULT_OK, resultIntent) finish() // Close SecondActivity after selection } // Set up the adapter with the onDeviceClick function adapter = DeviceAdapter(deviceList, onDeviceClick) recyclerView.adapter = adapter // Initialize loading screen elements loadingLayout = findViewById(R.id.loadingLayout) loaderText = findViewById(R.id.loaderText) // Initialize SwipeRefreshLayout swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout) // Set up the SwipeRefreshLayout listener swipeRefreshLayout.setOnRefreshListener { // Reset device list and start the discovery process deviceList.clear() adapter.notifyDataSetChanged() // Notify adapter to refresh RecyclerView Log.d("SwipeRefresh", "Swipe-to-refresh triggered") // Start SSDP discovery discoverDevicesUsingSSDP() } // Start SSDP discovery on initial launch discoverDevicesUsingSSDP() } private fun discoverDevicesUsingSSDP() { Log.d("SSDP", "Starting SSDP discovery") // Show the loading screen showLoadingScreen() // Use a Coroutine to perform the discovery in the background CoroutineScope(Dispatchers.IO).launch { try { // Open a socket to send and receive SSDP messages DatagramSocket().use { socket -> // Configure the socket to broadcast and join multicast group val multicastAddress = InetAddress.getByName(SSDP_ADDRESS) val packet = DatagramPacket( SSDP_MESSAGE.toByteArray(), SSDP_MESSAGE.length, multicastAddress, SSDP_PORT ) // Send the SSDP discovery request socket.send(packet) // Receive responses val buffer = ByteArray(1024) while (true) { val responsePacket = DatagramPacket(buffer, buffer.size) socket.receive(responsePacket) // Parse the response to extract the device details val response = String(responsePacket.data, 0, responsePacket.length) handleSSDPResponse(response, responsePacket.address.hostAddress) } } } catch (e: Exception) { e.printStackTrace() } finally { // Hide loading screen after discovery is done hideLoadingScreen() // Update the RecyclerView with the devices found so far runOnUiThread { // Notify adapter about data change after the scan finishes adapter.notifyDataSetChanged() Log.d("SSDP", "Discovery completed. Devices: ${deviceList.size}") // Stop the swipe refresh animation when done if (swipeRefreshLayout.isRefreshing) { swipeRefreshLayout.isRefreshing = false } } } } // Set a timeout to hide the loading screen after 5 seconds if still refreshing CoroutineScope(Dispatchers.Main).launch { delay(5000) // Hide loading screen even if discovery did not complete within 10 seconds hideLoadingScreen() // Stop refresh animation if still active if (swipeRefreshLayout.isRefreshing) { swipeRefreshLayout.isRefreshing = false } runOnUiThread { // Make sure the UI is updated adapter.notifyDataSetChanged() Log.d("SSDP", "Discovery timed out after 10 seconds") } } } private fun handleSSDPResponse(response: String, ipAddress: String) { Log.d("SSDP", "Received response: $response from $ipAddress") // Check if the response matches an OpenBeken device if (response.contains("OpenBk", ignoreCase = true)) { // Extract the LOCATION header from the response val locationRegex = Regex("(?i)LOCATION:\\s*(http://[\\d.]+:\\d+/ssdp\\.xml)") val locationMatch = locationRegex.find(response) val location = locationMatch?.groups?.get(1)?.value ?: "" // Send HTTP request to get the MAC address CoroutineScope(Dispatchers.IO).launch { val macAddress = getMacAddress(ipAddress) // Create an OpenBekenDevice object with the extracted details val device = OpenBekenDevice(ipAddress, macAddress) runOnUiThread { // Add the device to the list and update RecyclerView if (deviceList.none { it.ip == ipAddress }) { // Avoid duplicates deviceList.add(device) adapter.notifyDataSetChanged() // Notify adapter after updating the list } } } } } // Function to send HTTP request and get MAC address from the device's Status command private fun getMacAddress(ipAddress: String): String { return try { val url = URL("http://$ipAddress/cm?cmnd=Status") val connection = url.openConnection() as HttpURLConnection connection.requestMethod = "GET" connection.connect() // Read the response val response = connection.inputStream.bufferedReader().readText() // Log the full response for debugging Log.d("OpenBekenResponse", "Response from $ipAddress: $response") // Parse the JSON response val jsonResponse = JSONObject(response) // Extract MAC address from the StatusNET section if (jsonResponse.has("StatusNET")) { val statusNet = jsonResponse.getJSONObject("StatusNET") val macAddress = statusNet.getString("Mac") Log.d("OpenBekenResponse", "MAC Address: $macAddress") macAddress } else { Log.d("OpenBekenResponse", "StatusNET not found in response") "MAC address not found" } } catch (e: Exception) { e.printStackTrace() "Unknown MAC Address" } } // Show the loading screen private fun showLoadingScreen() { Log.d("UI", "Showing loading screen") runOnUiThread { loadingLayout.visibility = View.VISIBLE recyclerView.visibility = View.GONE // Hide the RecyclerView during loading } } // Hide the loading screen private fun hideLoadingScreen() { Log.d("UI", "Hiding loading screen") runOnUiThread { loadingLayout.visibility = View.GONE recyclerView.visibility = View.VISIBLE // Show the RecyclerView after loading } } }