-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDroneSignalDetector.kt
More file actions
102 lines (92 loc) · 3.45 KB
/
DroneSignalDetector.kt
File metadata and controls
102 lines (92 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package com.example.dronedetect
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.os.Handler
import android.os.Looper
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.Instant
class DroneSignalDetector(
private val context: Context,
private val onResults: (List<ScanResult>, FlightSnapshot) -> Unit = { _, _ -> }
) {
private val handler = Handler(Looper.getMainLooper())
private val wifiManager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
private val receiver = WifiScanReceiver(wifiManager, ::handleScanResults)
private var scanScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private var isScanning = false
private var latestFlightData: FlightSnapshot = FlightSnapshot(timestamp = Instant.now(), source = "bootstrap", payload = emptyMap())
fun startScan() {
if (isScanning) return
isScanning = true
context.registerReceiver(receiver, IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
scanScope.launch { fetchFlightData() }
requestScan()
}
suspend fun fetchFlightData() = withContext(Dispatchers.IO) {
// Placeholder for real RF/telemetry ingestion.
// Here we stamp a deterministic payload so downstream processing can
// align Wi-Fi RSSI snapshots with the last known telemetry sample.
latestFlightData = FlightSnapshot(
timestamp = Instant.now(),
source = "local_stub",
payload = mapOf(
"note" to "Replace with CSI/telemetry feed",
"status" to "idle"
)
)
Log.d(TAG, "Flight data refreshed at ${latestFlightData.timestamp}")
}
fun stopScan() {
if (!isScanning) return
isScanning = false
handler.removeCallbacksAndMessages(null)
runCatching { context.unregisterReceiver(receiver) }
scanScope.cancel()
scanScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
}
private fun requestScan() {
val started = wifiManager.startScan()
if (!started) {
Log.w(TAG, "Wi-Fi scan request rejected by platform")
}
handler.postDelayed({
if (isScanning) {
requestScan()
}
}, SCAN_INTERVAL_MS)
}
private fun handleScanResults(results: List<ScanResult>) {
if (!isScanning) return
onResults(results, latestFlightData)
}
companion object {
private const val TAG = "DroneSignalDetector"
private const val SCAN_INTERVAL_MS = 10_000L
}
}
data class FlightSnapshot(
val timestamp: Instant,
val source: String,
val payload: Map<String, String>
)
private class WifiScanReceiver(
private val wifiManager: WifiManager,
private val onResults: (List<ScanResult>) -> Unit
) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val action = intent?.action ?: return
if (action != WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) return
val results = wifiManager.scanResults.orEmpty()
onResults(results)
}
}