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 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -245,6 +256,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -301,17 +323,6 @@
-
-
-
-
-
-
-
-
-
-
-
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
+
+