Contenidos
Código de la aplicación
AndroidManifest.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" /> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.MyApplication"> <activity android:name=".MainActivity" android:exported="true" android:theme="@style/Theme.MyApplication"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
MainActivity.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
package com.example.myapplication import android.Manifest import android.annotation.SuppressLint import android.bluetooth.* import android.bluetooth.le.AdvertiseCallback import android.bluetooth.le.AdvertiseData import android.bluetooth.le.AdvertiseSettings import android.bluetooth.le.BluetoothLeAdvertiser import android.content.pm.PackageManager import android.os.Bundle import android.os.ParcelUuid import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import java.util.* class MainActivity : ComponentActivity() { // UUIDs de ejemplo private val SERVICE_UUID = UUID.fromString("12345678-1234-5678-1234-56789abcdef0") private val CHARACTERISTIC_UUID = UUID.fromString("abcdef01-2345-6789-abcd-ef0123456789") private lateinit var bluetoothManager: BluetoothManager private lateinit var bluetoothAdapter: BluetoothAdapter private var bluetoothGattServer: BluetoothGattServer? = null private var bluetoothLeAdvertiser: BluetoothLeAdvertiser? = null private var receivedData by mutableStateOf("No data received") private var connectionInfo by mutableStateOf("No device connected") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { BluetoothPeripheralApp(receivedData, connectionInfo) } checkPermissions() setupBluetooth() startGattServer() startAdvertising() } override fun onDestroy() { super.onDestroy() bluetoothGattServer?.close() } private fun checkPermissions() { val permissions = arrayOf( Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.BLUETOOTH_SCAN, Manifest.permission.BLUETOOTH_ADVERTISE ) if (permissions.any { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }) { ActivityCompat.requestPermissions(this, permissions, 1) } } private fun setupBluetooth() { bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager.adapter } @SuppressLint("MissingPermission") private fun startGattServer() { bluetoothGattServer = bluetoothManager.openGattServer(this, object : BluetoothGattServerCallback() { override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) { if (newState == BluetoothProfile.STATE_CONNECTED) { connectionInfo = "Connected to ${device.name ?: "Unknown"} (${device.address})" } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { connectionInfo = "Disconnected from device" } } override fun onServiceAdded(status: Int, service: BluetoothGattService) { if (status == BluetoothGatt.GATT_SUCCESS) { // Handle service added } } override fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) { if (characteristic.uuid == CHARACTERISTIC_UUID) { bluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, characteristic.value) } } override fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) { if (characteristic.uuid == CHARACTERISTIC_UUID) { receivedData = String(value, Charsets.UTF_8) if (responseNeeded) { bluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null) } } } }) val service = BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY) val characteristic = BluetoothGattCharacteristic( CHARACTERISTIC_UUID, BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_READ or BluetoothGattCharacteristic.PERMISSION_WRITE ) service.addCharacteristic(characteristic) bluetoothGattServer?.addService(service) } @SuppressLint("MissingPermission") private fun startAdvertising() { bluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser if (bluetoothLeAdvertiser == null) { println("Bluetooth LE Advertiser not available.") return } val advertisingSettings = AdvertiseSettings.Builder() .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) .setConnectable(true) .build() val advertisingData = AdvertiseData.Builder() .setIncludeDeviceName(true) .addServiceUuid(ParcelUuid(SERVICE_UUID)) .build() bluetoothLeAdvertiser?.startAdvertising(advertisingSettings, advertisingData, object : AdvertiseCallback() { override fun onStartSuccess(settingsInEffect: AdvertiseSettings) { super.onStartSuccess(settingsInEffect) println("Advertising started successfully") } override fun onStartFailure(errorCode: Int) { super.onStartFailure(errorCode) println("Advertising failed with error code $errorCode") } }) } @Composable fun BluetoothPeripheralApp(receivedData: String, connectionInfo: String) { Column(modifier = Modifier.padding(16.dp)) { Text("Received Messages:") Spacer(modifier = Modifier.height(8.dp)) Text(receivedData) Spacer(modifier = Modifier.height(16.dp)) Text("Connection Info:") Spacer(modifier = Modifier.height(8.dp)) Text(connectionInfo) } } } |
build.gradle.kts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
plugins { alias(libs.plugins.android.application) alias(libs.plugins.jetbrains.kotlin.android) } android { namespace = "com.example.myapplication" compileSdk = 34 defaultConfig { applicationId = "com.example.myapplication" minSdk = 33 targetSdk = 34 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" } buildFeatures { compose = true } composeOptions { kotlinCompilerExtensionVersion = "1.5.1" } packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } } } dependencies { implementation(libs.ui) implementation(libs.androidx.material) implementation(libs.ui.tooling.preview) implementation(libs.androidx.activity.compose.v191) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.activity.compose) implementation(platform(libs.androidx.compose.bom)) implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.ui.test.junit4) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) } |
Resultado de la ejecución de la aplicación
Explicación del código
1. Definición de UUIDs y Variables
private val SERVICE_UUID = UUID.fromString("12345678-1234-5678-1234-56789abcdef0")
private val CHARACTERISTIC_UUID = UUID.fromString("abcdef01-2345-6789-abcd-ef0123456789")
SERVICE_UUID
: UUID para el servicio BLE.CHARACTERISTIC_UUID
: UUID para la característica BLE que permite leer y escribir datos.
2. Declaración de Variables
private lateinit var bluetoothManager: BluetoothManager
private lateinit var bluetoothAdapter: BluetoothAdapter
private var bluetoothGattServer: BluetoothGattServer? = null
private var bluetoothLeAdvertiser: BluetoothLeAdvertiser? = null
private var receivedData by mutableStateOf("No data received")
private var connectionInfo by mutableStateOf("No device connected")
bluetoothManager
ybluetoothAdapter
: Para manejar Bluetooth.bluetoothGattServer
: El servidor GATT que manejará las solicitudes de otros dispositivos.bluetoothLeAdvertiser
: Para la publicidad BLE.receivedData
yconnectionInfo
: Estados que almacenan la información de los datos recibidos y la conexión actual.
3. Método onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BluetoothPeripheralApp(receivedData, connectionInfo)
}
checkPermissions()
setupBluetooth()
startGattServer()
startAdvertising()
}
setContent
: Configura la interfaz de usuario usando Jetpack Compose.checkPermissions
: Verifica y solicita permisos necesarios para Bluetooth.setupBluetooth
: Inicializa elBluetoothManager
yBluetoothAdapter
.startGattServer
: Inicia el servidor GATT para manejar solicitudes.startAdvertising
: Comienza a anunciar el dispositivo para que otros puedan encontrarlo.
4. Método onDestroy
override fun onDestroy() {
super.onDestroy()
bluetoothGattServer?.close()
}
- Cierra el servidor GATT cuando la actividad se destruye.
5. Método checkPermissions
private fun checkPermissions() {
val permissions = arrayOf(
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_CONNECT,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH_SCAN,
Manifest.permission.BLUETOOTH_ADVERTISE
)
if (permissions.any { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED }) {
ActivityCompat.requestPermissions(this, permissions, 1)
}
}
- Verifica si se tienen los permisos necesarios para usar Bluetooth. Si no, solicita permisos al usuario.
6. Método setupBluetooth
private fun setupBluetooth() {
bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
}
- Inicializa el
BluetoothManager
y elBluetoothAdapter
, que son necesarios para la operación de Bluetooth.
7. Método startGattServer
@SuppressLint("MissingPermission")
private fun startGattServer() {
bluetoothGattServer = bluetoothManager.openGattServer(this, object : BluetoothGattServerCallback() {
override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
connectionInfo = "Connected to ${device.name ?: "Unknown"} (${device.address})"
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
connectionInfo = "Disconnected from device"
}
}
override fun onServiceAdded(status: Int, service: BluetoothGattService) {
if (status == BluetoothGatt.GATT_SUCCESS) {
// Handle service added
}
}
override fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
if (characteristic.uuid == CHARACTERISTIC_UUID) {
bluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, characteristic.value)
}
}
override fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
if (characteristic.uuid == CHARACTERISTIC_UUID) {
receivedData = String(value, Charsets.UTF_8)
if (responseNeeded) {
bluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null)
}
}
}
})
val service = BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY)
val characteristic = BluetoothGattCharacteristic(
CHARACTERISTIC_UUID,
BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_WRITE,
BluetoothGattCharacteristic.PERMISSION_READ or BluetoothGattCharacteristic.PERMISSION_WRITE
)
service.addCharacteristic(characteristic)
bluetoothGattServer?.addService(service)
}
onConnectionStateChange
: Actualiza la información de conexión cuando un dispositivo se conecta o desconecta.onServiceAdded
: Maneja el estado cuando un servicio se agrega.onCharacteristicReadRequest
: Responde a las solicitudes de lectura de la característica.onCharacteristicWriteRequest
: ActualizareceivedData
con el valor recibido cuando se escribe en la característica.
8. Método startAdvertising
@SuppressLint("MissingPermission")
private fun startAdvertising() {
bluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser
if (bluetoothLeAdvertiser == null) {
println("Bluetooth LE Advertiser not available.")
return
}
val advertisingSettings = AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.setConnectable(true)
.build()
val advertisingData = AdvertiseData.Builder()
.setIncludeDeviceName(true)
.addServiceUuid(ParcelUuid(SERVICE_UUID))
.build()
bluetoothLeAdvertiser?.startAdvertising(advertisingSettings, advertisingData, object : AdvertiseCallback() {
override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
super.onStartSuccess(settingsInEffect)
println("Advertising started successfully")
}
override fun onStartFailure(errorCode: Int) {
super.onStartFailure(errorCode)
println("Advertising failed with error code $errorCode")
}
})
}
- Configura y comienza a anunciar el servicio BLE. Esto hace que el dispositivo sea detectable y accesible para otros dispositivos BLE.
9. Método BluetoothPeripheralApp
@Composable
fun BluetoothPeripheralApp(receivedData: String, connectionInfo: String) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Received Messages:")
Spacer(modifier = Modifier.height(8.dp))
Text(receivedData)
Spacer(modifier = Modifier.height(16.dp))
Text("Connection Info:")
Spacer(modifier = Modifier.height(8.dp))
Text(connectionInfo)
}
}
- Define la interfaz de usuario usando Jetpack Compose. Muestra los datos recibidos y la información de conexión.