La mise en œuvre du Bluetooth sous Jetpack Compose est assez ardue. La Documentation officielle est loin d'être suffisante
Je vais exposer l'exemple d'une application basique qui communique avec le module Bluetooth HC05 connecté à un Arduino. L'application va nous permettre de contrôler un petit système constitué d'une LED, un Buzzer et un capteur analogique
L'UI de l'application est constituée par :
Avant de commencer à travailler sur l'application, il faut associer le module HC-05 avec le téléphone. Précisons un point important:
Voici le code Arduino
#include <SoftwareSerial.h>
// Rx(0), Tx(1) = Port COMM physique <--> moniteur Série
// Rx(7), Tx(8) = Port COMM Software <--> module HC-05
/*
_________________ ______________
| Arduino | | Moniteur |
| Rx(0) |<---------------| Série |
| Tx(1) |--------------->| |
| | ---------------
| | _______________
| Rx(7) |<---------------|Tx Module |
| Tx(8) |----/\/\/------>|Rx HC-05 |
| | | |
| 5V |----------------|Vcc |
| GND |----------------|GND |
| | ---------------
-----------------
*/
SoftwareSerial SerialGSM(7,8); // (RX, TX)
#define LEDPIN 13
#define BUZPIN 11
void setup() {
Serial.begin(9600);
SerialGSM.begin(9600);
Serial.println("HC-05");
pinMode(LEDPIN, OUTPUT);
digitalWrite(LEDPIN, LOW);
pinMode(BUZPIN, OUTPUT);
digitalWrite(BUZPIN,HIGH);//buzzer active LOW
}
float R = 35.00; // valeur fictive du capteur analogique
void loop() {
if (SerialGSM.available() > 0){
char c = SerialGSM.read();
Serial.println(c);
switch (c) {
case 'A':
digitalWrite(LEDPIN, HIGH);
break;
case 'B':
digitalWrite(LEDPIN,LOW);
break;
case 'C':
digitalWrite(BUZPIN, LOW); // active LOW
break;
case 'D':
digitalWrite(BUZPIN,HIGH);
break;
case 'E':
SerialGSM.print(R);
R += 0.5;
break;
default:
break;
}
}
}
Pour créer l'application qui sera installée sur le smartphone, les choses sont un peu plus compliquées. D'abord, il faut télécharger et installer Android Studio sur votre PC. Une fois installé, il faut commencer par faire quelques petits programmes simples pour se faire la main. Pour mettre en œuvre le Bluetooth dans une application Android il faut être familier avec la programmation sous Android: Activités, Boutons, TextView, ListView, Adaptateur de ListView, les Listners, les threads et les Handler de thread ... Voir cette rubrique
Lors de la création du projet, choisir l'Appi 31 ou ultérieure comme minimum SDK
Ajouter la permission d'utiliser le Bluetooth au fichier AndroidManifest.xml
...
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application
...
Pour faciliter l'analyse, toute l'application sera placée dans un seule fichier: MainActivity.kt
L'application sera constituée des blocs suivants:
private val MY_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
const val CONNECTION_FAILED: Int = 0
const val CONNECTION_SUCCESS: Int = 1
const val WRITE_ERROR: Int = 2
const val DISCONNECTION_SUCCESS: Int = 3
const val DISCONNECTION_FAILED: Int = 4
//######## Classe pour connecter un device Bluetooth ###############################################
@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()
}
dataExchangeInstance = DataExchange(socket, handler)
}
}
}
Cette classe contient les méthodes permettant de communiquer avec le HC-05
//######## Classe d'échange à travers le bluetooth ##########################################
class DataExchange(private val mmSocket: BluetoothSocket, private val handler: Handler) : Thread() {
private val mmInStream: InputStream = mmSocket.inputStream
private val mmOutStream: OutputStream = mmSocket.outputStream
// Envoyer une donnée à travers le bluetooth
fun write(str: String) {
try {
mmOutStream.write(str.toByteArray())
} catch (_: IOException) {
handler.obtainMessage(WRITE_ERROR).sendToTarget()
}
}
// Envoyer une requête (string) et lire la réponse (String)
fun request(outstr: String, length: Int): String {
val mmBuffer = ByteArray(length)
// vider le buffer d'entrée
while (mmInStream.available() > 0) mmInStream.read() // vider lebuffer
// transmettre le string de sortie
try {
mmOutStream.write(outstr.toByteArray())
} catch (_: IOException) {
}
// lire le string reçu
var numBytesReaded = 0
try {
while (numBytesReaded < length) {
val num = mmInStream.read(mmBuffer, numBytesReaded, length - numBytesReaded)
if (num == -1) {
break
}
numBytesReaded += num
}
return String(mmBuffer, 0, numBytesReaded)
} catch (e: IOException) {
return "erreur"
}
}
// déconnecter l'équipement connecté
fun cancel() {
try {
mmSocket.close()
handler.obtainMessage(DISCONNECTION_SUCCESS).sendToTarget()
} catch (e: IOException) {
handler.obtainMessage(DISCONNECTION_FAILED).sendToTarget()
}
}
}
En plus de la classe, il faut déclarer une instance de la classe qui sera initialisée par la classe qui connecte le HC-05
//######## Instances de la classe d'échange ########################################################
var dataExchangeInstance: DataExchange? = null
A placer avant la fonction onCreate()
private lateinit var bluetoothAdapter: BluetoothAdapter
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
private lateinit var bluetoothActivationLauncher: ActivityResultLauncher<Intent>
private lateinit var handler: Handler
private val status = mutableStateOf("")
A placer dans la fonction onCreate en dessous de super.onCreate()
bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
Ce luncher nous servira plus tard pour activer le Bluetooth et connecter le HC-05. Un luncher démarre une tâche asynchrone (comme une coroutine ou une tâche en arrière-plan) qui se déroule sans bloquer le flux principal du code
// Enregistrement du luncher d'activation du Bluetooth
bluetoothActivationLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { response ->
if (response.resultCode == RESULT_OK) {
status.value += "Bluetooth activé\nTentative de connexion au HC-05\n"
status.value += connectHC05(bluetoothAdapter, handler)
} else {
Toast.makeText(this, "L'appli ne peut pas fonctionner sans Bluetooth", Toast.LENGTH_LONG).show()
this.finish()
}
}
Ce luncher nous servira plus tard pour demander l'autorisation d'utiliser le bluetooth
// Enregistrement du luncher de demande d'autorisation
requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
status.value += "Autorisation acceptée\n"
activerBluetooth()
} else {
Toast.makeText(this, "L'app ne peut pas fonctionner sans cette autorisation", Toast.LENGTH_LONG).show()
this.finish()
}
}
Les échanges via Bluetooth sont gérés par des threads, qui sont des processus d'arrière-plan s'exécutant en parallèle du reste du code. Le Handler permet la communication entre ces threads et l'interface utilisateur. Les threads envoient des messages sous forme de codes ou d'identifiants spécifiques, que le Handler intercepte pour mettre à jour la variable status. Cette variable est ensuite utilisée dans l'interface utilisateur pour afficher les informations relatives à la connexion ou aux actions effectuées.
// Configuration du handler de messages
handler = Handler(Looper.getMainLooper()) { msg ->
when (msg.what) {
CONNECTION_FAILED -> {
status.value += "Connexion à HC-05 échoué\n"
true
}
CONNECTION_SUCCESS -> {
status.value += "Connexion à HC-05 réussie\n"
true
}
WRITE_ERROR -> {
status.value += "Erreur de transmission\n"
true
}
DISCONNECTION_SUCCESS -> {
status.value += "Déconnexion réussie\n"
true
}
DISCONNECTION_FAILED -> {
status.value += "La déconnexion a échoué\n"
true
}
else -> false
}
}
C'est ici que commence réellement le travail
// Vérifier si l'appli dispose de l'autorisation Bluetooth
// activer le bluetooth et connecter le HC-05
val bluetoothPermission = android.Manifest.permission.BLUETOOTH_CONNECT
if (ContextCompat.checkSelfPermission(
this,
bluetoothPermission
) == PackageManager.PERMISSION_GRANTED
) {
status.value += "Autorisation déjà accordée\n"
activerBluetooth() // activer Bluetooth et connecter le HC-05
} else {
status.value += "On va demander l'autorisation\n"
requestPermissionLauncher.launch(bluetoothPermission) // demander l'autorisation
}
La fonction MyUI() compose l'Interface utilisateur. On l'appelle dans le bloc setContent { }. On a terminé la fonction onCreate()
// ================= UI Jetpack Compose ====================================================
setContent {
MyUI(bluetoothAdapter, handler, status)
}
Cette fonction est placée dans la classe MainActivity après onCreate
// Fonction pour Activer le Bluetooth et se connecter au HC-05
private fun activerBluetooth() {
if (bluetoothAdapter.isEnabled) {
status.value += "Bluetooth déjà actif\nTentative de connexion\n"
status.value += connectHC05(bluetoothAdapter, handler)
} else {
status.value += "Bluetooth non actif\nOn demande l'activation\n"
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
bluetoothActivationLauncher.launch(enableBtIntent)
}
}
Cette fonction découvre la listes des équipements Bluetooth associés. Si le HC-05 en fait partie elle le connecte.
Elle est placée en dehors de la classe MainActivity car on l'appelle aussi à partir l'interface Utilisateur MyUI()
//#### Fonction pour connecter le HC-05#############################################################
@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"
}
}
L'interface utilisateur (UI) est assez basique. Elle est composée par la fonction composable MyUI(). Elle contient:
//The UI ###########################################################################################
@Composable
fun MyUI(
bluetoothAdapter: BluetoothAdapter?,
handler: Handler,
connectStatus: MutableState<String>
) {
val capteur1 = remember { mutableStateOf("Rien") }
var isLedChecked by remember { mutableStateOf(false) }
var isBuzzerChecked by remember { mutableStateOf(false) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = connectStatus.value,
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.background(Color(0x80E2EBEA))
.padding(start = 16.dp) // marge intérieure
)
// Bouton pour effacer le statut
Button(onClick = { connectStatus.value = "" }) {
Text(text = "Clear Status")
}
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth()
) {
Button(
onClick = {
dataExchangeInstance?.cancel()
connectStatus.value += "Tentative de Connexion\n"
connectStatus.value += connectHC05(
bluetoothAdapter,
handler
)
}
) {
Text("Connecter")
}
Button(
onClick = {
dataExchangeInstance?.cancel()
connectStatus.value += "Tentative de Déconnexion\n"
}
) {
Text("Déconnecter")
}
}
// LED et Buzzer avec Switch
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "LED ")
Switch(
checked = isLedChecked,
onCheckedChange = {
isLedChecked = it
if (isLedChecked) dataExchangeInstance?.write("A")
else dataExchangeInstance?.write("B")
},
thumbContent = if (isLedChecked) {
{
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize),
)
}
} else null
)
Spacer(modifier = Modifier.width(48.dp))
Text(text = "BUZZER ")
Switch(
checked = isBuzzerChecked,
onCheckedChange = {
isBuzzerChecked = it
if (isBuzzerChecked) dataExchangeInstance?.write("C")
else dataExchangeInstance?.write("D")
},
thumbContent = if (isBuzzerChecked) {
{
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize),
)
}
} else null
)
}
// Lire le capteur
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Button(
onClick = {
val str = dataExchangeInstance?.request("E", 5)
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
)
}
}
}
Voici le code entier de l'application
@file:Suppress("LiftReturnOrAssignment")
package com.example.bluetoothhc05a
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothSocket
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.ActivityResultLauncher
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.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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 java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.UUID
//######## constantes partagés ####################################################################
private val MY_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
const val CONNECTION_FAILED: Int = 0
const val CONNECTION_SUCCESS: Int = 1
const val WRITE_ERROR: Int = 2
const val DISCONNECTION_SUCCESS: Int = 3
const val DISCONNECTION_FAILED: Int = 4
//######## Classe pour connecter un device Bluetooth ###############################################
@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()
}
dataExchangeInstance = DataExchange(socket, handler)
}
}
}
//######## Classe d'échange à travers le bluetooth ##########################################
class DataExchange(private val mmSocket: BluetoothSocket, private val handler: Handler) : Thread() {
private val mmInStream: InputStream = mmSocket.inputStream
private val mmOutStream: OutputStream = mmSocket.outputStream
// Envoyer une donnée à travers le bluetooth
fun write(str: String) {
try {
mmOutStream.write(str.toByteArray())
} catch (_: IOException) {
handler.obtainMessage(WRITE_ERROR).sendToTarget()
}
}
// Envoyer une requête (string) et lire la réponse (String)
fun request(outstr: String, length: Int): String {
val mmBuffer = ByteArray(length)
// vider le buffer d'entrée
while (mmInStream.available() > 0) mmInStream.read() // vider lebuffer
// transmettre le string de sortie
try {
mmOutStream.write(outstr.toByteArray())
} catch (_: IOException) {
}
// lire le string reçu
var numBytesReaded = 0
try {
while (numBytesReaded < length) {
val num = mmInStream.read(mmBuffer, numBytesReaded, length - numBytesReaded)
if (num == -1) {
break
}
numBytesReaded += num
}
return String(mmBuffer, 0, numBytesReaded)
} catch (e: IOException) {
return "erreur"
}
}
// déconnecter l'équipement connecté
fun cancel() {
try {
mmSocket.close()
handler.obtainMessage(DISCONNECTION_SUCCESS).sendToTarget()
} catch (e: IOException) {
handler.obtainMessage(DISCONNECTION_FAILED).sendToTarget()
}
}
}
//######## Instances de la classe d'échange ########################################################
var dataExchangeInstance: DataExchange? = null
//###### Classe main activity Mainactivity #########################################################
class MainActivity : ComponentActivity() {
// Déclarations des variables Bluetooth et autres objets
private lateinit var bluetoothAdapter: BluetoothAdapter
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
private lateinit var bluetoothActivationLauncher: ActivityResultLauncher<Intent>
private lateinit var handler: Handler
private val status = mutableStateOf("")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialisation du Bluetooth Adapter
bluetoothAdapter = getSystemService(BluetoothManager::class.java).adapter
// Enregistrement du luncher d'activation du Bluetooth
bluetoothActivationLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { response ->
if (response.resultCode == RESULT_OK) {
status.value += "Bluetooth activé\nTentative de connexion au HC-05\n"
status.value += connectHC05(bluetoothAdapter, handler)
} else {
Toast.makeText(
this,
"L'appli ne peut pas fonctionner sans Bluetooth",
Toast.LENGTH_LONG
).show()
this.finish()
}
}
// Enregistrement du luncher de demande d'autorisation
requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
status.value += "Autorisation acceptée\n"
activerBluetooth()
} else {
Toast.makeText(
this,
"L'app ne peut pas fonctionner sans cette autorisation",
Toast.LENGTH_LONG
).show()
this.finish()
}
}
// Configuration du handler de messages
handler = Handler(Looper.getMainLooper()) { msg ->
when (msg.what) {
CONNECTION_FAILED -> {
status.value += "Connexion à HC-05 échoué\n"
true
}
CONNECTION_SUCCESS -> {
status.value += "Connexion à HC-05 réussie\n"
true
}
WRITE_ERROR -> {
status.value += "Erreur de transmission\n"
true
}
DISCONNECTION_SUCCESS -> {
status.value += "Déconnexion réussie\n"
true
}
DISCONNECTION_FAILED -> {
status.value += "La déconnexion a échoué\n"
true
}
else -> false
}
}
// ##################### C'set ici que le travail commence #################################
// Vérifier si l'appli dispose de l'autorisation Bluetooth
// activer le bluetooth et connecter le HC-05
val bluetoothPermission = android.Manifest.permission.BLUETOOTH_CONNECT
if (ContextCompat.checkSelfPermission(
this,
bluetoothPermission
) == PackageManager.PERMISSION_GRANTED
) {
status.value += "Autorisation déjà accordée\n"
activerBluetooth() // activer Bluetoouth et connecter le HC-05
} else {
status.value += "On va demander l'autorisation\n"
requestPermissionLauncher.launch(bluetoothPermission) // demander l'autorisation
}
// ================= UI Jetpack Compose ====================================================
setContent {
MyUI(bluetoothAdapter, handler, status)
}
}
// ==============================================================================================
// Fonction pour Activer le Bluetooth et se connecter au HC-05
private fun activerBluetooth() {
if (bluetoothAdapter.isEnabled) {
status.value += "Bluetooth déjà actif\nTentative de connexion\n"
status.value += connectHC05(bluetoothAdapter, handler)
} else {
status.value += "Bluetooth non actif\nOn demande l'activation\n"
val enableBtIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
bluetoothActivationLauncher.launch(enableBtIntent)
}
}
}
//#### Fonction pour connecter le HC-05#############################################################
@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"
}
}
//The UI ###########################################################################################
@Composable
fun MyUI(
bluetoothAdapter: BluetoothAdapter?,
handler: Handler,
connectStatus: MutableState<String>
) {
val capteur1 = remember { mutableStateOf("Rien") }
var isLedChecked by remember { mutableStateOf(false) }
var isBuzzerChecked by remember { mutableStateOf(false) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = connectStatus.value,
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.background(Color(0x80E2EBEA))
.padding(start = 16.dp) // marge intérieure
)
// Bouton pour effacer le statut
Button(onClick = { connectStatus.value = "" }) {
Text(text = "Clear Status")
}
Row(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth()
) {
Button(
onClick = {
dataExchangeInstance?.cancel()
connectStatus.value += "Tentative de Connexion\n"
connectStatus.value += connectHC05(
bluetoothAdapter,
handler
)
}
) {
Text("Connecter")
}
Button(
onClick = {
dataExchangeInstance?.cancel()
connectStatus.value += "Tentative de Déconnexion\n"
}
) {
Text("Déconnecter")
}
}
// LED et Buzzer avec Switch
Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(text = "LED ")
Switch(
checked = isLedChecked,
onCheckedChange = {
isLedChecked = it
if (isLedChecked) dataExchangeInstance?.write("A")
else dataExchangeInstance?.write("B")
},
thumbContent = if (isLedChecked) {
{
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize),
)
}
} else null
)
Spacer(modifier = Modifier.width(48.dp))
Text(text = "BUZZER ")
Switch(
checked = isBuzzerChecked,
onCheckedChange = {
isBuzzerChecked = it
if (isBuzzerChecked) dataExchangeInstance?.write("C")
else dataExchangeInstance?.write("D")
},
thumbContent = if (isBuzzerChecked) {
{
Icon(
imageVector = Icons.Filled.Check,
contentDescription = null,
modifier = Modifier.size(SwitchDefaults.IconSize),
)
}
} else null
)
}
// Lire le capteur
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Button(
onClick = {
val str = dataExchangeInstance?.request("E", 5)
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
)
}
}
}