DEV Community

Abdul Rehman
Abdul Rehman

Posted on

Android Kotlin Reading Data from HC-05 Bluetooth module

Here is the first working code

package com.myfyp.posture
import android.content.Intent
import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothSocket
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.myfyp.posture.ui.theme.PostureDetectionBTTheme
import com.myfyp.posture.ui.navigation.AppNavigation
import java.util.UUID

import androidx.compose.runtime.*
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.IOException


// This is the standard Serial Port Profile (SPP) UUID
private val SPP_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
private const val TAG = "HC05_Reader"

class MainActivity : ComponentActivity() {
    private val requestBluetoothEnable = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == RESULT_OK) {
            Toast.makeText(this, "Bluetooth enabled!", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(this, "Bluetooth not enabled.", Toast.LENGTH_SHORT).show()
        }
    }

    private val requestPermissionsLauncher = registerForActivityResult(
        ActivityResultContracts.RequestMultiplePermissions()
    ) { permissions ->
        var allGranted = true
        permissions.entries.forEach {
            if (!it.value) {
                allGranted = false
                Toast.makeText(this, "${it.key} not granted.", Toast.LENGTH_SHORT).show()
            }
        }
        if (allGranted) {
            Toast.makeText(this, "All Bluetooth permissions granted!", Toast.LENGTH_SHORT).show()
            // You can now proceed with Bluetooth operations
        } else {
            Toast.makeText(this, "Bluetooth permissions are required.", Toast.LENGTH_LONG).show()
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    BluetoothReaderScreen(
                        requestBluetoothEnable = { intent -> requestBluetoothEnable.launch(intent) },
                        requestPermissions = { permissions -> requestPermissionsLauncher.launch(permissions) }
                    )
                }
            }
        }
        checkPermissions()
    }
    private fun checkPermissions() {
        val permissionsToRequest = mutableListOf<String>()

        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
            if (checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
                permissionsToRequest.add(Manifest.permission.BLUETOOTH_SCAN)
            }
            if (checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                permissionsToRequest.add(Manifest.permission.BLUETOOTH_CONNECT)
            }
        } else { // Android 11 and lower
            if (checkSelfPermission(Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED) {
                permissionsToRequest.add(Manifest.permission.BLUETOOTH)
            }
            if (checkSelfPermission(Manifest.permission.BLUETOOTH_ADMIN) != PackageManager.PERMISSION_GRANTED) {
                permissionsToRequest.add(Manifest.permission.BLUETOOTH_ADMIN)
            }
            if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                permissionsToRequest.add(Manifest.permission.ACCESS_FINE_LOCATION)
            }
        }

        if (permissionsToRequest.isNotEmpty()) {
            requestPermissionsLauncher.launch(permissionsToRequest.toTypedArray())
        }
    }

//    override fun onCreate(savedInstanceState: Bundle?) {
//        super.onCreate(savedInstanceState)
//        setContent {
//            PostureDetectionBTTheme {
//                Surface(
//                    modifier = Modifier.fillMaxSize(),
//                    color = MaterialTheme.colorScheme.background
//                ) {
//                    AppNavigation()
//                }
//            }
//        }
//    }
}


@Composable
fun BluetoothReaderScreen(
    requestBluetoothEnable: (Intent) -> Unit,
    requestPermissions: (Array<String>) -> Unit
) {
    val context = LocalContext.current
    val bluetoothManager: BluetoothManager? = remember { context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager }
    val bluetoothAdapter: BluetoothAdapter? = remember(bluetoothManager) { bluetoothManager?.adapter }

    var connectionStatus by remember { mutableStateOf("Disconnected") }
    var receivedData by remember { mutableStateOf("No data yet") }
    var hc05Device: BluetoothDevice? by remember { mutableStateOf(null) }
    var bluetoothSocket: BluetoothSocket? by remember { mutableStateOf(null) }

    val coroutineScope = rememberCoroutineScope()

    DisposableEffect(Unit) {
        onDispose {
            // Ensure socket is closed when the composable leaves the screen
            try {
                bluetoothSocket?.close()
                Log.d(TAG, "Bluetooth socket closed on dispose.")
            } catch (e: IOException) {
                Log.e(TAG, "Error closing socket: ${e.message}")
            }
        }
    }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text(text = "Connection Status: $connectionStatus")
        Spacer(modifier = Modifier.height(8.dp))
        Text(text = "Received Data: $receivedData")
        Spacer(modifier = Modifier.height(16.dp))

        Button(
            onClick = {
                // Request permissions if not granted
                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) {
                    requestPermissions(arrayOf(Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_CONNECT))
                } else {
                    requestPermissions(arrayOf(Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_FINE_LOCATION))
                }

                if (bluetoothAdapter == null) {
                    Toast.makeText(context, "Bluetooth not supported on this device.", Toast.LENGTH_LONG).show()
                    return@Button
                }

                if (!bluetoothAdapter.isEnabled) {
                    val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
                    requestBluetoothEnable(enableBtIntent)
                }
            }
        ) {
            Text("Check Bluetooth & Permissions")
        }

        Spacer(modifier = Modifier.height(8.dp))

        Button(
            onClick = {
                if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled) {
                    Toast.makeText(context, "Bluetooth is not enabled or supported.", Toast.LENGTH_SHORT).show()
                    return@Button
                }

                // IMPORTANT: Replace "HC-05" with the actual name of your HC-05 module
                // or use its MAC address if you know it (e.g., "00:11:22:33:AA:BB")
                val deviceNameOrAddress = "xx:xx:xx:xx:xx:xx" // Or "XX:XX:XX:XX:XX:XX"

                val pairedDevices: Set<BluetoothDevice> = if (
                    context.checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED
                ) {
                    bluetoothAdapter.bondedDevices
                } else {
                    Toast.makeText(context, "BLUETOOTH_CONNECT permission not granted.", Toast.LENGTH_SHORT).show()
                    emptySet()
                }


                hc05Device = pairedDevices.find {
                    if (context.checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
                        it.name == deviceNameOrAddress || it.address == deviceNameOrAddress
                    } else {
                        false
                    }
                }

                if (hc05Device == null) {
                    Toast.makeText(context, "$deviceNameOrAddress not found in paired devices. Pair it first!", Toast.LENGTH_LONG).show()
                    connectionStatus = "Device not paired"
                    return@Button
                }

                coroutineScope.launch(Dispatchers.IO) {
                    try {
                        withContext(Dispatchers.Main) { connectionStatus = "Connecting..." }

                        if (context.checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                            withContext(Dispatchers.Main) {
                                Toast.makeText(context, "BLUETOOTH_CONNECT permission not granted.", Toast.LENGTH_SHORT).show()
                            }
                            return@launch
                        }

                        bluetoothSocket = hc05Device?.createRfcommSocketToServiceRecord(SPP_UUID)
                        bluetoothSocket?.connect()

                        withContext(Dispatchers.Main) {
                            connectionStatus = "Connected to ${hc05Device?.name}"
                            Toast.makeText(context, "Connected to HC-05!", Toast.LENGTH_SHORT).show()
                        }

                        // Start reading data
                        val inputStream = bluetoothSocket?.inputStream
                        val buffer = ByteArray(1024)
                        var bytes: Int

                        while (bluetoothSocket?.isConnected == true) {
                            try {
                                bytes = inputStream?.read(buffer) ?: -1
                                if (bytes > 0) {
                                    val readMessage = String(buffer, 0, bytes)
                                    withContext(Dispatchers.Main) {
                                        receivedData = readMessage
                                        Log.d(TAG, "Received: $readMessage")
                                    }
                                }
                            } catch (e: IOException) {
                                withContext(Dispatchers.Main) {
                                    connectionStatus = "Connection lost: ${e.message}"
                                    Toast.makeText(context, "Connection lost: ${e.message}", Toast.LENGTH_LONG).show()
                                }
                                Log.e(TAG, "Error reading from socket: ${e.message}")
                                break // Exit loop on error
                            }
                        }
                    } catch (e: IOException) {
                        withContext(Dispatchers.Main) {
                            connectionStatus = "Connection Failed: ${e.message}"
                            Toast.makeText(context, "Connection failed: ${e.message}", Toast.LENGTH_LONG).show()
                        }
                        Log.e(TAG, "Socket connection failed: ${e.message}")
                        try { bluetoothSocket?.close() } catch (e: IOException) { Log.e(TAG, "Error closing socket after fail: ${e.message}") }
                    }
                }
            }
        ) {
            Text("Connect & Read from HC-05")
        }

        Spacer(modifier = Modifier.height(8.dp))

        Button(
            onClick = {
                coroutineScope.launch(Dispatchers.IO) {
                    try {
                        bluetoothSocket?.close()
                        withContext(Dispatchers.Main) {
                            connectionStatus = "Disconnected"
                            Toast.makeText(context, "Disconnected.", Toast.LENGTH_SHORT).show()
                        }
                    } catch (e: IOException) {
                        withContext(Dispatchers.Main) {
                            Toast.makeText(context, "Error disconnecting: ${e.message}", Toast.LENGTH_SHORT).show()
                        }
                        Log.e(TAG, "Error closing socket: ${e.message}")
                    }
                }
            },
            enabled = bluetoothSocket?.isConnected == true // Enable only when connected
        ) {
            Text("Disconnect")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)