我正在尝试编写一个示例来说明 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
该应用程序不会启动蓝牙,也不会扫描附近的设备。这可以在启动应用程序之前在手机上完成。
我已经设法:
我还没有做到:
将连接线程中的消息共享到我的 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")
}
}
}
没有人帮助我,这不太好, 没关系,这是一个可以运行的版本,
/*
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
)
}
}
}