diff --git a/.idea/other.xml b/.idea/other.xml index eea74a4..faea4af 100644 --- a/.idea/other.xml +++ b/.idea/other.xml @@ -25,6 +25,17 @@ diff --git a/app/src/main/java/com/example/bluetoothscanner/MainActivity.kt b/app/src/main/java/com/example/bluetoothscanner/MainActivity.kt index 4a37a19..4c1c55a 100644 --- a/app/src/main/java/com/example/bluetoothscanner/MainActivity.kt +++ b/app/src/main/java/com/example/bluetoothscanner/MainActivity.kt @@ -6,58 +6,60 @@ import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothManager import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanResult +import android.bluetooth.le.ScanSettings import android.content.Context -import android.content.Intent import android.content.pm.PackageManager -import android.net.Uri +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager import android.os.Build import android.os.Bundle +import android.provider.Settings import android.util.Log import android.widget.EditText import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts -import androidx.core.content.ContextCompat -import androidx.core.content.FileProvider import androidx.compose.runtime.* +import androidx.core.content.ContextCompat import com.example.bluetoothscanner.ui.theme.BLEScannerScreen +import okhttp3.* import java.io.File import java.io.FileWriter import java.io.IOException import java.text.SimpleDateFormat import java.util.* -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Callback -import okhttp3.Call -import okhttp3.Response import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody -import android.provider.Settings +import android.content.Intent +import android.net.Uri +import androidx.core.content.FileProvider -import java.util.Date -import java.util.Locale -import java.util.TimeZone class MainActivity : ComponentActivity() { private lateinit var bluetoothAdapter: BluetoothAdapter + private lateinit var sensorManager: SensorManager + private lateinit var rotationVectorSensor: Sensor + private var scanning by mutableStateOf(false) private var scanResults by mutableStateOf(listOf()) + private var azimuthValues by mutableStateOf(listOf()) + private val targetMacAddresses = setOf( - "60:98:66:33:42:D4", "60:98:66:32:8E:28", "60:98:66:32:BC:AC", "60:98:66:30:A9:6E", - "60:98:66:32:CA:74", "60:98:66:2F:CF:9F", "60:98:66:32:B8:EF", "60:98:66:32:CA:59", - "60:98:66:33:35:4C", "60:98:66:32:AF:B6", "60:98:66:33:0E:8C", "60:98:66:32:C8:E9", - "60:98:66:32:9F:67", "60:98:66:33:24:44", "60:98:66:32:BB:CB", "60:98:66:32:AA:F8", - "A0:6C:65:99:DB:7C", "60:98:66:32:98:58" + "60:98:66:32:98:58", "60:98:66:32:8E:28", "60:98:66:32:BC:AC", "60:98:66:30:A9:6E", "60:98:66:32:CA:74" ) + private var azimuth = 0f + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { BLEScannerScreen( scanResults = scanResults, + azimuthValues = azimuthValues, scanning = scanning, onStartScan = { startBleScan() }, onStopScan = { promptFileNameAndSave() } @@ -67,28 +69,27 @@ class MainActivity : ComponentActivity() { val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager.adapter - // 테스트 메시지 서버로 전송 - //sendTestDataToServer() + // SensorManager 초기화 및 TYPE_ROTATION_VECTOR 센서 등록 + sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager + rotationVectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)!! + sensorManager.registerListener(sensorEventListener, rotationVectorSensor, SensorManager.SENSOR_DELAY_UI) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { requestBluetoothPermissions() } else { startBleScan() } - - } private fun requestBluetoothPermissions() { - val requestMultiplePermissions = - registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> - val allGranted = permissions.entries.all { it.value } - if (allGranted) { - startBleScan() - } else { - Log.e("MainActivity", "Permission denied") - } + val requestMultiplePermissions = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions -> + val allGranted = permissions.entries.all { it.value } + if (allGranted) { + startBleScan() + } else { + Log.e("MainActivity", "Permission denied") } + } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { requestMultiplePermissions.launch( @@ -99,27 +100,22 @@ class MainActivity : ComponentActivity() { ) ) } else { - requestMultiplePermissions.launch( - arrayOf(Manifest.permission.ACCESS_FINE_LOCATION) - ) + requestMultiplePermissions.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)) } } private fun startBleScan() { try { - // 스캔 시작 전에 기존 데이터를 초기화 scanResults = listOf() - if (ContextCompat.checkSelfPermission( - this, - Manifest.permission.BLUETOOTH_SCAN - ) == PackageManager.PERMISSION_GRANTED || - ContextCompat.checkSelfPermission( - this, - Manifest.permission.ACCESS_FINE_LOCATION - ) == PackageManager.PERMISSION_GRANTED - ) { - bluetoothAdapter.bluetoothLeScanner.startScan(bleScanCallback) + if (ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + + val scanSettings = ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .build() + + bluetoothAdapter.bluetoothLeScanner.startScan(null, scanSettings, bleScanCallback) scanning = true Log.i("MainActivity", "Scanning started...") } else { @@ -135,7 +131,7 @@ class MainActivity : ComponentActivity() { bluetoothAdapter.bluetoothLeScanner.stopScan(bleScanCallback) scanning = false Log.i("MainActivity", "Scanning stopped.") - saveAndShareCsv(fileName) // CSV 파일 저장 및 공유 + saveAndShareCsv(fileName) } catch (e: SecurityException) { Log.e("MainActivity", "SecurityException: ${e.message}") } @@ -148,20 +144,14 @@ class MainActivity : ComponentActivity() { if (macAddress in targetMacAddresses) { scanResults = scanResults + result + azimuthValues = azimuthValues + azimuth // 현재 azimuth 값을 추가합니다. val rssi = result.rssi - val timestamp = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date()) - val number = scanResults.size // 스캔된 데이터의 개수 - - Log.i("BLE Scan", "Device: $macAddress, RSSI: $rssi") - - // 서버로 데이터 전송 - sendDataToServer(macAddress, rssi, number) + sendDataToServer(macAddress, rssi, azimuth) } } } - private fun promptFileNameAndSave() { val editText = EditText(this) editText.hint = "Enter file name" @@ -184,20 +174,82 @@ class MainActivity : ComponentActivity() { .show() } + private fun sendDataToServer(macAddress: String, rssi: Int, azimuth: Float) { + val client = OkHttpClient() + val androidId: String = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) + val json = """ + { + "macAddress": "$macAddress", + "rssi": $rssi, + "deviceId": "$androidId", + "azimuth": $azimuth + } + """ + + val mediaType = "application/json; charset=utf-8".toMediaType() + val body = json.toRequestBody(mediaType) + val request = Request.Builder() + .url("https://49fe-117-16-196-162.ngrok-free.app/api/current_rssi") // 서버 URL 변경 + .post(body) + .build() + + client.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + Log.e("MainActivity", "Failed to send data to server", e) + } + + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + Log.i("MainActivity", "Data sent successfully") + } else { + Log.e("MainActivity", "Server error: ${response.code}") + } + } + }) + } + + private val sensorEventListener = object : SensorEventListener { + override fun onSensorChanged(event: SensorEvent) { + if (event.sensor == rotationVectorSensor) { + val rotationMatrix = FloatArray(9) + SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values) + val orientation = FloatArray(3) + SensorManager.getOrientation(rotationMatrix, orientation) + + // azimuth 값을 0~359 범위의 Float으로 변환 + azimuth = ((Math.toDegrees(orientation[0].toDouble()).toInt() + 360) % 360).toFloat() + + Log.i("Direction", "Azimuth: $azimuth") + } + } + + override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {} + } + + + + override fun onResume() { + super.onResume() + sensorManager.registerListener(sensorEventListener, rotationVectorSensor, SensorManager.SENSOR_DELAY_UI) + } + + override fun onPause() { + super.onPause() + sensorManager.unregisterListener(sensorEventListener) + } + private fun saveAndShareCsv(fileName: String) { val file = File(getExternalFilesDir(null), "$fileName.csv") try { // 새 파일에만 데이터를 기록 FileWriter(file, false).use { writer -> - // CSV 파일에 데이터를 씁니다. - writer.append("No.,TimeStamp,MAC Address,RSSI\n") + // CSV 파일의 헤더 작성 + writer.append("No.,TimeStamp,MAC Address,RSSI,Azimuth\n") scanResults.forEachIndexed { index, result -> - val timestamp = SimpleDateFormat( - "HH:mm:ss", - Locale.getDefault() - ).format(result.timestampNanos / 1000000) - writer.append("${index + 1},$timestamp,${result.device.address},${result.rssi}\n") + val timestamp = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(result.timestampNanos / 1000000) + val azimuth = azimuthValues.getOrNull(index) ?: 0f // 방향 데이터 추가 + writer.append("${index + 1},$timestamp,${result.device.address},${result.rssi},$azimuth\n") } } @@ -208,6 +260,7 @@ class MainActivity : ComponentActivity() { } } + private fun shareCsvFile(file: File) { val uri: Uri = FileProvider.getUriForFile( this, @@ -223,43 +276,4 @@ class MainActivity : ComponentActivity() { } startActivity(Intent.createChooser(shareIntent, "Share CSV via")) } - - private fun sendDataToServer(macAddress: String, rssi: Int, number: Int) { - val client = OkHttpClient() - - // Android ID 가져오기 - val androidId: String = - Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID) - Log.i("androidID", "안드로이드아이디: ${androidId}") - val json = """ - { - "macAddress": "$macAddress", - "rssi": $rssi, - "deviceId": "$androidId", - "number": $number - } - """ - - val mediaType = "application/json; charset=utf-8".toMediaType() - val body = json.toRequestBody(mediaType) - val request = Request.Builder() - .url("https://b2c7-117-16-195-2.ngrok-free.app/api/current_rssi") // 서버의 URL 여기에 - .post(body) - .build() - - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - Log.e("MainActivity", "Failed to send data to server", e) - } - - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - Log.i("MainActivity", "Data sent successfully") - } else { - Log.e("MainActivity", "Server error: ${response.code}") - } - } - }) - } } - diff --git a/app/src/main/java/com/example/bluetoothscanner/ui/theme/BLEScannerScreen.kt b/app/src/main/java/com/example/bluetoothscanner/ui/theme/BLEScannerScreen.kt index beca93d..be0d8f0 100644 --- a/app/src/main/java/com/example/bluetoothscanner/ui/theme/BLEScannerScreen.kt +++ b/app/src/main/java/com/example/bluetoothscanner/ui/theme/BLEScannerScreen.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.tooling.preview.Preview @Composable fun BLEScannerScreen( scanResults: List, + azimuthValues: List, // azimuth 값을 추가합니다. scanning: Boolean, onStartScan: () -> Unit, onStopScan: () -> Unit @@ -69,14 +70,17 @@ fun BLEScannerScreen( .padding(vertical = 4.dp, horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically ) { - TableCell(text = " ", weight = 0.2f, fontSize = 12.sp, textAlign = TextAlign.Center) + TableCell(text = "No.", weight = 0.2f, fontSize = 12.sp, textAlign = TextAlign.Center) TableCell(text = "TimeStamp", weight = 0.8f, fontSize = 12.sp, textAlign = TextAlign.Center) TableCell(text = "MAC Address", weight = 1.5f, fontSize = 12.sp, textAlign = TextAlign.Center) TableCell(text = "RSSI", weight = 0.5f, fontSize = 12.sp, textAlign = TextAlign.Center) + TableCell(text = "Azimuth", weight = 0.5f, fontSize = 12.sp, textAlign = TextAlign.Center) // Azimuth 헤더 추가 } } + // Ensure azimuthValues and scanResults have the same size itemsIndexed(scanResults) { index, result -> + val azimuth = azimuthValues.getOrNull(index) ?: 0f // Default to 0 if azimuth value is missing Row( modifier = Modifier .fillMaxWidth(), @@ -86,6 +90,7 @@ fun BLEScannerScreen( TableCell(text = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(result.timestampNanos / 1000000), weight = 0.8f, fontSize = 12.sp, textAlign = TextAlign.Center) TableCell(text = result.device.address, weight = 1.5f, fontSize = 12.sp, textAlign = TextAlign.Center) TableCell(text = result.rssi.toString(), weight = 0.5f, fontSize = 12.sp, textAlign = TextAlign.Center) + TableCell(text = azimuth.toString(), weight = 0.5f, fontSize = 12.sp, textAlign = TextAlign.Center) // Azimuth 값 추가 } } } @@ -94,25 +99,31 @@ fun BLEScannerScreen( ) } -@Composable -fun RowScope.TableCell(text: String, weight: Float, fontSize: androidx.compose.ui.unit.TextUnit, textAlign: TextAlign) { - Text( - text = text, - modifier = Modifier - .weight(weight) - .padding(4.dp), - fontSize = fontSize, - textAlign = textAlign - ) -} - @Preview(showBackground = true) @Composable fun PreviewBLEScannerScreen() { BLEScannerScreen( scanResults = listOf(), + azimuthValues = listOf(), // 빈 리스트를 전달하여 미리보기를 위한 기본값 설정 scanning = false, onStartScan = {}, onStopScan = {} ) } + +@Composable +fun RowScope.TableCell( + text: String, + weight: Float, + fontSize: androidx.compose.ui.unit.TextUnit, + textAlign: TextAlign +) { + Text( + text = text, + modifier = Modifier + .weight(weight) + .padding(4.dp), + fontSize = fontSize, + textAlign = textAlign + ) +} diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml index c5ca8a2..a9c5e14 100644 --- a/app/src/main/res/xml/file_paths.xml +++ b/app/src/main/res/xml/file_paths.xml @@ -1,6 +1,4 @@ - - \ No newline at end of file + +