From f54948b1a276c8d82712b42146f3bb2d7e23eed4 Mon Sep 17 00:00:00 2001 From: lash21 Date: Wed, 21 Oct 2026 12:18:25 +0200 Subject: [PATCH] changes --- .../smart_watch_android/BLE/BleManager.kt | 121 +++++++----------- .../BluetoothForgroundService.kt | 47 ------- .../smart_watch_android/MainActivity.kt | 25 ++-- .../smart_watch_android/MainViewModel.kt | 24 ---- .../Screens/AppNavigation.kt | 19 +-- .../Screens/ConnectScreen.kt | 19 ++- .../example/smart_watch_android/viewModel.kt | 16 +++ 7 files changed, 102 insertions(+), 169 deletions(-) delete mode 100644 app/src/main/java/com/example/smart_watch_android/BluetoothForgroundService.kt delete mode 100644 app/src/main/java/com/example/smart_watch_android/MainViewModel.kt create mode 100644 app/src/main/java/com/example/smart_watch_android/viewModel.kt diff --git a/app/src/main/java/com/example/smart_watch_android/BLE/BleManager.kt b/app/src/main/java/com/example/smart_watch_android/BLE/BleManager.kt index 7b0dec5..3df932b 100644 --- a/app/src/main/java/com/example/smart_watch_android/BLE/BleManager.kt +++ b/app/src/main/java/com/example/smart_watch_android/BLE/BleManager.kt @@ -14,10 +14,9 @@ import android.os.Build import android.util.Log import androidx.annotation.RequiresApi import androidx.annotation.RequiresPermission -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.core.app.ActivityCompat +import com.example.smart_watch_android.MainActivity +import com.example.smart_watch_android.ViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -28,63 +27,67 @@ import java.time.format.DateTimeFormatter import java.util.UUID import kotlin.time.Duration.Companion.milliseconds -class BleManager(private val context: Context) { - val deviceAddress = "10:BD:A3:8F:01:0E" +class BleManager(private val context: Context, val viewModel: ViewModel) { + + //var deviceAddress = "10:BD:A3:8F:01:0E" + val devices = mutableListOf() + val currentDate: LocalDate? = LocalDate.now() - val deviceUUIDs = DeviceUUIDs + private val deviceUUIDs = DeviceUUIDs private var bluetoothGatt: BluetoothGatt? = null private var bluetoothManager: BluetoothManager? = null private var bluetoothAdapter: BluetoothAdapter? = null + var deviceAddress: String = "" + val gattCallback = object : BluetoothGattCallback() { + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { + if (status == BluetoothGatt.GATT_SUCCESS) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + Log.d("ESP32_BLE", "Connected! Start discoverServices()...") + gatt.discoverServices() + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + viewModel.setDeviceConection(false) - var isConnected by mutableStateOf(false) - private set - + gatt.close() + bluetoothGatt = null + Log.d("ESP32_BLE", "Disconnected.") + } + } else { + Log.d("ESP32_BLE", "error in onConnectionStateChange! Status-Code: $status") + viewModel.setDeviceConection(false) + } + } + override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { + if (status == BluetoothGatt.GATT_SUCCESS) { + viewModel.setDeviceConection(true) + Log.d("ESP32_BLE", "connected successfully.") + } + } + } + data class Devices( + val deviceName: String, + val deviceMac: String + ) + @RequiresApi(Build.VERSION_CODES.S) @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) fun init() { + checkAndRequestBluetoothPermissions() bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager?.adapter - - if (bluetoothAdapter == null) return - - try { - val device = bluetoothAdapter?.getRemoteDevice(deviceAddress) - if (device != null) { - val state = bluetoothManager!!.getConnectionState(device, BluetoothProfile.GATT) - if (state == BluetoothProfile.STATE_CONNECTED) { - Log.d("ESP32_BLE", "App restarted: device still connected. rebuild GATT-connection...") - connectToEsp32(deviceAddress) - } else { - isConnected = false - Log.d("ESP32_BLE", "App restarted: not connected.") - } - } - } catch (e: Exception) { - Log.d("ESP32_BLE", "error while checking the status: ${e.message}") - } } + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - fun checkCurrentConnectionState(deviceAddress: String) { - if (bluetoothManager == null) return - - try { - val device = bluetoothAdapter?.getRemoteDevice(deviceAddress) - if (device != null) { - val state = bluetoothManager!!.getConnectionState(device, BluetoothProfile.GATT) - if (state == BluetoothProfile.STATE_CONNECTED) { - isConnected = true - Log.d("ESP32_BLE", "App restarted: device still connected!") - } else { - isConnected = false - Log.d("ESP32_BLE", "App restarted: device not connected.") - } - } - } catch (e: Exception) { - Log.d("ESP32_BLE", "error while checking the status: ${e.message}") + fun getDeviceList(): List{ + bluetoothAdapter?.bondedDevices?.forEach { device -> + devices.add(Devices(deviceName = device.name, deviceMac = device.address)) } + return devices } + + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) @RequiresApi(Build.VERSION_CODES.S) fun checkAndRequestBluetoothPermissions() { val permissions = arrayOf( @@ -107,7 +110,7 @@ class BleManager(private val context: Context) { } } - fun connectToEsp32(deviceAddress: String) { + fun connectToEsp32() { Log.d("ESP32_BLE", "connectToEsp32 started with address: $deviceAddress") if (bluetoothAdapter == null) { Log.d("ESP32_BLE", "error: Bluetooth is not supported!") @@ -117,34 +120,6 @@ class BleManager(private val context: Context) { try { val device = bluetoothAdapter!!.getRemoteDevice(deviceAddress) - val gattCallback = object : BluetoothGattCallback() { - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { - if (status == BluetoothGatt.GATT_SUCCESS) { - if (newState == BluetoothProfile.STATE_CONNECTED) { - Log.d("ESP32_BLE", "Connected! Start discoverServices()...") - gatt.discoverServices() - } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - isConnected = false - - gatt.close() - bluetoothGatt = null - Log.d("ESP32_BLE", "Disconnected.") - } - } else { - Log.d("ESP32_BLE", "error in onConnectionStateChange! Status-Code: $status") - isConnected = false - } - } - - override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { - if (status == BluetoothGatt.GATT_SUCCESS) { - isConnected = true - Log.d("ESP32_BLE", "connected successfully.") - } - } - } - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { bluetoothGatt = device.connectGatt(context, false, gattCallback) } else { diff --git a/app/src/main/java/com/example/smart_watch_android/BluetoothForgroundService.kt b/app/src/main/java/com/example/smart_watch_android/BluetoothForgroundService.kt deleted file mode 100644 index ab1ac07..0000000 --- a/app/src/main/java/com/example/smart_watch_android/BluetoothForgroundService.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.example.smart_watch_android - -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.Service -import android.content.Intent -import android.os.IBinder -import androidx.core.app.NotificationCompat - -class BluetoothForegroundService : Service() { - companion object { - const val CHANNEL_ID = "BluetoothServiceChannel" - const val NOTIFICATION_ID = 1 - } - - override fun onCreate() { - super.onCreate() - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - createNotificationChannel() - - val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle("Bluetooth connection active") - .setContentText("the app keeps the connection in the background.") - .setSmallIcon(android.R.drawable.stat_sys_data_bluetooth) - .build() - - startForeground(NOTIFICATION_ID, notification) - return START_STICKY - } - - override fun onBind(intent: Intent?): IBinder? { - return null - } - - private fun createNotificationChannel() { - val serviceChannel = NotificationChannel( - CHANNEL_ID, - "Bluetooth Service Channel", - NotificationManager.IMPORTANCE_LOW - ) - val manager = getSystemService(NotificationManager::class.java) - manager?.createNotificationChannel(serviceChannel) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/smart_watch_android/MainActivity.kt b/app/src/main/java/com/example/smart_watch_android/MainActivity.kt index eae7c0f..6eb2256 100644 --- a/app/src/main/java/com/example/smart_watch_android/MainActivity.kt +++ b/app/src/main/java/com/example/smart_watch_android/MainActivity.kt @@ -1,5 +1,6 @@ package com.example.smart_watch_android +import android.Manifest import android.content.Intent import android.os.Build import android.os.Bundle @@ -8,37 +9,41 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels import androidx.annotation.RequiresApi +import androidx.annotation.RequiresPermission import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier +import com.example.smart_watch_android.BLE.BleManager import com.example.smart_watch_android.Screens.AppNavigation class MainActivity : ComponentActivity() { - private val viewModel: MainViewModel by viewModels() + lateinit var bleManager: BleManager + private val viewModel: ViewModel by viewModels() @RequiresApi(Build.VERSION_CODES.S) - @androidx.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel.initBleManager(this) - viewModel.bleManager.checkAndRequestBluetoothPermissions() + bleManager = BleManager(this, viewModel) + bleManager.init() enableEdgeToEdge() setContent { MaterialTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background - ) { - AppNavigation(bleManager = viewModel.bleManager) + ) { + AppNavigation(bleManager = bleManager, viewModel = viewModel) } } } - startBluetoothService() } - private fun startBluetoothService() { - val serviceIntent = Intent(this, BluetoothForegroundService::class.java) - startForegroundService(serviceIntent) + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + override fun onDestroy() { + super.onDestroy() + bleManager.disconnectEsp32() + } } diff --git a/app/src/main/java/com/example/smart_watch_android/MainViewModel.kt b/app/src/main/java/com/example/smart_watch_android/MainViewModel.kt deleted file mode 100644 index 55003e1..0000000 --- a/app/src/main/java/com/example/smart_watch_android/MainViewModel.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.example.smart_watch_android - -import android.Manifest -import android.app.Application -import android.content.Context -import androidx.annotation.RequiresPermission -import androidx.lifecycle.AndroidViewModel -import com.example.smart_watch_android.BLE.BleManager - -class MainViewModel( - application: Application, -) : AndroidViewModel(application) { - - lateinit var bleManager: BleManager - private set - - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) - fun initBleManager(activityContext: Context) { - if (!::bleManager.isInitialized) { - bleManager = BleManager(context = activityContext) - bleManager.init() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/example/smart_watch_android/Screens/AppNavigation.kt b/app/src/main/java/com/example/smart_watch_android/Screens/AppNavigation.kt index 98f292d..0da8046 100644 --- a/app/src/main/java/com/example/smart_watch_android/Screens/AppNavigation.kt +++ b/app/src/main/java/com/example/smart_watch_android/Screens/AppNavigation.kt @@ -1,25 +1,25 @@ package com.example.smart_watch_android.Screens import android.Manifest -import android.R import androidx.annotation.RequiresPermission import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.navigation.NavController +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import com.example.smart_watch_android.BLE.BleManager -import com.example.smart_watch_android.MainViewModel +import com.example.smart_watch_android.ViewModel @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) @Composable -fun AppNavigation(bleManager: BleManager) { +fun AppNavigation(bleManager: BleManager, viewModel: ViewModel) { val navController = rememberNavController() + val isConnected by viewModel.isDeviceConnected.collectAsState() - LaunchedEffect(bleManager.isConnected) { - if (bleManager.isConnected) { + LaunchedEffect(viewModel.isConnected) { + if (isConnected) { navController.navigate("control_panel") { popUpTo("connect") { inclusive = true } } @@ -29,7 +29,7 @@ fun AppNavigation(bleManager: BleManager) { } } } - val startDestination = if (bleManager.isConnected) { + val startDestination = if (isConnected) { "control_panel" } else { "connect" @@ -42,7 +42,8 @@ fun AppNavigation(bleManager: BleManager) { // Connect Screen composable("connect") { ConnectScreen( - bleManager = bleManager + bleManager = bleManager, + viewModel = viewModel ) } diff --git a/app/src/main/java/com/example/smart_watch_android/Screens/ConnectScreen.kt b/app/src/main/java/com/example/smart_watch_android/Screens/ConnectScreen.kt index 2b3a369..485f5e1 100644 --- a/app/src/main/java/com/example/smart_watch_android/Screens/ConnectScreen.kt +++ b/app/src/main/java/com/example/smart_watch_android/Screens/ConnectScreen.kt @@ -1,5 +1,8 @@ package com.example.smart_watch_android.Screens +import android.Manifest +import android.util.Log +import androidx.annotation.RequiresPermission import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -28,12 +31,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.example.smart_watch_android.BLE.BleManager +import com.example.smart_watch_android.ViewModel +@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) @Composable -fun ConnectScreen(bleManager: BleManager) { +fun ConnectScreen(bleManager: BleManager, viewModel: ViewModel) { var expanded by remember { mutableStateOf(false) } var selectedDevice by remember { mutableStateOf("device") } - val items = listOf("Option 1", "Option 2", "Option 3") + val devices = bleManager.getDeviceList() Column( modifier = Modifier .fillMaxSize() @@ -53,11 +58,13 @@ fun ConnectScreen(bleManager: BleManager) { expanded = expanded, onDismissRequest = { expanded = false } ) { - items.forEach { item -> + devices.forEach { item -> DropdownMenuItem( - text = { Text(item) }, + text = { Text(item.deviceName) }, onClick = { - selectedDevice = item + selectedDevice = item.deviceName + bleManager.deviceAddress = item.deviceMac + Log.d("ConnectScreen", item.deviceMac) expanded = false } ) @@ -67,8 +74,8 @@ fun ConnectScreen(bleManager: BleManager) { Spacer(modifier = Modifier.height(64.dp)) Button( - onClick = { bleManager.connectToEsp32(deviceAddress = "10:BD:A3:8F:01:0E") }, modifier = Modifier.size(400.dp), + onClick = {bleManager.connectToEsp32() }, shape = CircleShape, colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF5E18BE)) ) { diff --git a/app/src/main/java/com/example/smart_watch_android/viewModel.kt b/app/src/main/java/com/example/smart_watch_android/viewModel.kt new file mode 100644 index 0000000..7b03626 --- /dev/null +++ b/app/src/main/java/com/example/smart_watch_android/viewModel.kt @@ -0,0 +1,16 @@ +package com.example.smart_watch_android + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class ViewModel : ViewModel() { + var isConnected = MutableStateFlow(false) + + val isDeviceConnected: StateFlow = isConnected.asStateFlow() + + fun setDeviceConection(status: Boolean) { + isConnected.value = status + } +} \ No newline at end of file