蓝牙 Jetpack Compose Kotlin

问题描述 投票:0回答:1

我正在尝试编写一个示例来说明 Jetpack/Kotlin 下蓝牙的使用。我试图尽可能地遵循文档并使其尽可能简单 https://developer.android.com/training/permissions/requesting https://developer.android.com/develop/connectivity/bluetooth/connect-bluetooth-devices https://developer.android.com/develop/connectivity/bluetooth/transfer-data

该应用程序不会启动蓝牙,也不会扫描附近的设备。这可以在启动应用程序之前在手机上完成。

我已经设法:

  • 请求许可(BLUETOOTH_CONNECT)
  • 检索配对设备列表
  • 将(RFCOMM)作为客户端连接到其中一个(作为服务器运行)

我还没有做到:

  • 将连接线程中的消息共享到我的 UI。我正在使用 Log.i() 来查看发生了什么。

  • 实现BluetoothService()类来与蓝牙设备传输和接收数据。

      o Cannot call the write(bytes: ByteArray) function from my UI
      o Do not know how to implement the handler to share messages with the UI
    

感谢您的帮助,因为这个示例可以帮助许多使用手机通过 Arduino 控制多个设备的学生。

这是我的代码:

package com.example.bluetooth2

import ...

private val MY_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Bluetooth2Theme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MyUI()
                }
            }
        }
    }
}
//##################################################################################################

@Composable
fun MyUI() {
    val deviceList = remember { mutableStateListOf<String>() }
    val connectStatus = remember { mutableStateOf("Non connecté") }
    val blutoothPermission = android.Manifest.permission.BLUETOOTH_CONNECT
    val context = LocalContext.current
    val bluetoothManager: BluetoothManager = context.getSystemService(BluetoothManager::class.java)
    val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter

    //register the Request permission launcher
    val requestPermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
        if (isGranted) {
            connectHC05(bluetoothAdapter, deviceList, connectStatus)
        } else {
            connectStatus.value = "Bluetooth Permission not accepted"
        }
    }

    //Check whether the user has already granted the runtime permission
    if (ContextCompat.checkSelfPermission(context, blutoothPermission) == PackageManager.PERMISSION_GRANTED) {
        connectHC05(bluetoothAdapter, deviceList, connectStatus)
    } else {
        LaunchedEffect(true) {
            requestPermissionLauncher.launch(blutoothPermission)
        }
    }
//The UI ##################################################################################################
    Column {
        Row (horizontalArrangement = Arrangement.SpaceAround,
            modifier = Modifier.fillMaxWidth()
        ){
            Button(onClick = {
                //val dataToSend = "LED_ON".toByteArray()

            }
            )
            {
                Text("WRITE")
            }
            Button(onClick = {

            }
            )
            {
                Text("READ")
            }
        }

        Text(
            text = connectStatus.value,
            modifier = Modifier
                .padding(8.dp)
                .fillMaxWidth()
                .background(Color(0x80E2EBEA))
                .padding(start = 16.dp),  // marge intérieure
            color = if (connectStatus.value.contains("is connected")) Color.Green else Color.Red
        )
        LazyColumn {
            items(deviceList) { item ->
                Text(
                    text = item,
                    modifier = Modifier
                        .padding(8.dp)
                        .fillMaxWidth()
                        .background(Color(0x80BCF5F0))
                        .padding(start = 16.dp)  // marge intérieure
                )
            }
        }
    }
}
//##################################################################################################
@SuppressLint("MissingPermission")
private fun connectHC05(
    bluetoothAdapter: BluetoothAdapter?, deviceList: MutableList<String>,
    connectStatus: MutableState<String>
) {
    val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
    deviceList.clear()
    pairedDevices?.forEach { device ->
        deviceList.add("${device.name} \n${device.address}")
    }
    if (deviceList.isEmpty()) connectStatus.value = "Aucun device associé, démarrer le Bluetooth si ne n'est pas déjà fait"
    else {
        val hc05Device = pairedDevices?.find { it.name == "HC-05" }
        if (hc05Device != null) {
            connectStatus.value = "Tentative de connexion à HC-05"
            ConnectThread(hc05Device).start()
        }else connectStatus.value = "HC-05 Not paired\n Not Connected"
    }
}

//##################################################################################################
@SuppressLint("MissingPermission")
class ConnectThread(private val monDevice: BluetoothDevice) : Thread() {
    private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
        monDevice.createRfcommSocketToServiceRecord(MY_UUID)
    }
    override fun run() {
        try {
            mmSocket?.connect()
            Log.i("blt","Connexion à HC-05 réussie")
        } catch (e: IOException) {
            Log.i("blt","Echec connexion")
        }
    }
}
kotlin bluetooth jetpack
1个回答
0
投票

没有人帮助我,这不太好, 没关系,这是一个可以运行的版本,

/*
Activez le Bluetooth et scannez les équipements avant de lancer l'application
L'application vérifie si la permission BLUETOOTH_CONNECT est accordée. Si ce n'est pas le cas, elle demande la permission.
Si la permission est accordée, elle récupère liste des équipements associés.
Si le HC-05 en fait partie elle essaye de s'y connecter
Un champ connectStatus affiche l'état de la connexion dans l'UI
Le bouton `LED ON` transmet le caractère 'A' vers le HC-05
Le bouton `LED OFF` transmet le caractère 'B' vers le HC-05
Le bouton bouton `READ` transmet le caractère 'C', lit 5 caractères et les affiche dans le champ capteur1 de l'UI
fait par:   Abdelmajid OUMNAD   [email protected]
*/


@file:Suppress("LiftReturnOrAssignment")

package com.example.bluetooth5

import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothSocket
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import com.example.bluetooth5.ui.theme.Bluetooth5Theme
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.UUID


class MainActivity : ComponentActivity() {
    private lateinit var bluetoothManager: BluetoothManager
    private lateinit var bluetoothAdapter: BluetoothAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        bluetoothManager = getSystemService(BluetoothManager::class.java)
        bluetoothAdapter = bluetoothManager.adapter
        val status = mutableStateOf("Bluetooth & Arduino\n")
        // Handler pour remonter les messages à partir des Threads
        val handler = Handler(Looper.getMainLooper()) { msg ->
            when (msg.what) {
                CONNECTION_FAILED -> {
                    status.value += "La connexion à HC-05 a échoué\n"
                    true
                }
                CONNECTION_SUCCESS -> {
                    status.value += "Connexion à HC-05 réussie\n"
                    true
                }
                else -> false
            }
        }
        val blutoothPermission = android.Manifest.permission.BLUETOOTH_CONNECT
        // enregistrement du laucher de demande de permission
        // ce launcher sera appelé si la permission n'a pas déja été accordée
        val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission())
        { isGranted: Boolean ->
            if (isGranted) {
                status.value += "Permission acceptée\nTentative de connexion\n"
                status.value += connectHC05( bluetoothAdapter, handler)
            } else {
                status.value += "====>  Permission refusée\n"
            }
        }
        //Vérifier si l'appli a déjà l'autorisation
        if (ContextCompat.checkSelfPermission(applicationContext,blutoothPermission) == PackageManager.PERMISSION_GRANTED) {
            status.value += "Permission déjà accordée \nTentative de connexion\n"
            status.value += connectHC05( bluetoothAdapter, handler)
        } else {
            status.value += "On va demander la permission\n"
            requestPermissionLauncher.launch(blutoothPermission)
        }
        setContent {
            Bluetooth5Theme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    MyUI(status)
                }
            }
        }
    }
}
//##################################################################################################
@SuppressLint("MissingPermission")
private fun connectHC05(bluetoothAdapter: BluetoothAdapter?, handler: Handler): String {
    // récupérer la liste des équipements associés
    val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
    // localiser le HC-05 dans la liste
    val hc05Device = pairedDevices?.find { it.name == "HC-05" }
    // Si le HC-05 est associé, essayer de le connecter
    if (hc05Device != null) {
        ConnectThread(hc05Device, handler).start()
        // les messages d'état sont remonté de ConnectThread() vers le handler
        return ""
    }else {
        return "HC-05 Non Associé\n"
    }

}
//##################################################################################################
private val MY_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
const val CONNECTION_FAILED: Int = 0
const val CONNECTION_SUCCESS: Int = 1
@SuppressLint("MissingPermission")
class ConnectThread(private val monDevice: BluetoothDevice, private val handler: Handler) : Thread() {
    private val mmSocket: BluetoothSocket? by lazy(LazyThreadSafetyMode.NONE) {
        monDevice.createRfcommSocketToServiceRecord(MY_UUID)
    }
    override fun run() {
        mmSocket?.let { socket ->
            try {
                socket.connect()
                handler.obtainMessage(CONNECTION_SUCCESS).sendToTarget()
            } catch (e: Exception) {
                handler.obtainMessage(CONNECTION_FAILED).sendToTarget()
            }
            dataExchaneInstance = DataExchange(socket)
        }
    }
}

//##################################################################################################
var dataExchaneInstance: DataExchange? = null
class DataExchange(mmSocket: BluetoothSocket) : Thread() {
    private val length = 5
    private val mmInStream: InputStream = mmSocket.inputStream
    private val mmOutStream: OutputStream = mmSocket.outputStream
    private val mmBuffer: ByteArray = ByteArray(length)

    fun write(bytes: ByteArray) {
        try {
            mmOutStream.write(bytes)
        } catch (_: IOException) {
        }
    }
    fun read(): String {
        try {
            mmOutStream.write("C".toByteArray())
        } catch (_: IOException) {
        }

        var numBytesReaded = 0
        try {
            while (numBytesReaded < length) {
                val num = mmInStream.read(mmBuffer, numBytesReaded, length - numBytesReaded)
                if (num == -1) {
                    // La fin du flux a été atteinte
                    break
                }
                numBytesReaded += num
            }
            return String(mmBuffer, 0, numBytesReaded)
        } catch (e: IOException) {
            return "erreur" // Retourner une chaîne vide en cas d'erreur
        }
    }
}

//The UI ##################################################################################################
@Composable
fun MyUI(connectStatus: MutableState<String>) {
    //val connectStatus = remember { mutableStateOf(status.toString()) }
    val capteur1 = remember { mutableStateOf("Rien") }
    Column {
        Text(
            text = connectStatus.value,
            modifier = Modifier
                .padding(8.dp)
                .fillMaxWidth()
                .background(Color(0x80E2EBEA))
                .padding(start = 16.dp)  // marge intérieure
        )

        Spacer(modifier = Modifier.height(16.dp))
        Row (horizontalArrangement = Arrangement.SpaceAround,
            modifier = Modifier.fillMaxWidth()
        ){
            Button(onClick = {
                dataExchaneInstance?.write("A".toByteArray())
            }
            )
            {
                Text("  LED ON  ")
            }
            Button(onClick = {
                dataExchaneInstance?.write("B".toByteArray())
            }
            )
            {
                Text("  LED OFF  ")
            }
        }
        Spacer(modifier = Modifier.height(16.dp))
        Row (verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth())
        {
            Button(onClick = {
                val str = dataExchaneInstance?.read()
                if (str != null) {
                    capteur1.value = str
                }else connectStatus.value = "rien"
            },
                modifier=Modifier.padding(start = 48.dp)
            )
            {
                Text("  READ  ")
            }
            Text(
                text = capteur1.value,
                modifier = Modifier
                    .padding(start = 96.dp)
                    .background(Color(0x80E2EBEA))
                    .padding(horizontal = 16.dp)  // marge intérieure
            )
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.