# Bluetooth BLE / GATT

> La pila BLE/GATT asíncrona (BluetoothManager) que sustituyó al JSR-82/SPP.

Source: https://crossfly.dev/es/docs/data-and-services/device-bluetooth-ble — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# Bluetooth BLE / GATT

CrossFly usa una pila **BLE/GATT asíncrona** (`dev.crossfly.device.bluetooth`), que **sustituyó**
al antiguo JSR-82/SPP. El modelado se basa en `CompletableFuture`.

## ⚠️ Nota de honestidad (léela antes de codificar)
En el **core**, varias operaciones son **esqueleto sobre estado en memoria** — `BluetoothConnectionBuilder.build()`
crea streams vacíos, `GattCharacteristic.read()/write()` solo tocan la caché local, y `discoverServices()`
retorna una lista vacía. El **transporte de radio real** viene de los launchers de plataforma (Android/iOS). El
**modelado** (UUIDs, futures, jerarquía service→characteristic→descriptor) ya es definitivo; cuenta con
la API, pero prueba en dispositivo real por plataforma.

## Descubrimiento y conexión
```java
BluetoothManager bt = BluetoothManager.getInstance();   // throws IOException

// escanear (asíncrono)
bt.scanForDevices(10, TimeUnit.SECONDS, new BluetoothManager.ScanCallback() {
   public void onSuccess(List<BluetoothDevice> devices) { /* elegir uno */ }
   public void onError(String error) { Debug.log(error); }
});

// conectar (asíncrono) — devuelve una BluetoothConnection
bt.connect(device, new BluetoothManager.ConnectionCallback() {
   public void onSuccess(BluetoothConnection conn) { usar(conn); }
   public void onError(String error) { Debug.log(error); }
});
```
Hay variantes **`*Sync`** (`scanForDevicesSync`, `connectSync`) — úsalas **solo en un worker thread**, nunca
en el paint thread.

## GATT — leer batería y suscribir notificaciones
Modelo **service → characteristic → descriptor**, I/O vía `CompletableFuture`:
```java
device.discoverServices(services -> {
   GattService bateria = device.getService("0000180F-0000-1000-8000-00805F9B34FB");
   GattCharacteristic nivel = bateria.getCharacteristic("00002A19-0000-1000-8000-00805F9B34FB");

   nivel.read().thenAccept(bytes -> {
      int pct = bytes[0] & 0xFF;
      Component.onUI(() -> label.setText(pct + "%"));
   });

   nivel.enableNotifications(bytes ->                     // suscribe cambios
      Component.onUI(() -> label.setText((bytes[0] & 0xFF) + "%")));
});
```
`GattCharacteristic` expone `read()` → `CompletableFuture<byte[]>`, `write(byte[])` → `CompletableFuture<Void>`,
`enableNotifications(Consumer<byte[]>)`, y las pruebas de capacidad `canRead()`/`canWrite()`/`canNotify()`.

## `BluetoothConnection` es `AutoCloseable`
```java
try (BluetoothConnection conn = bt.connectSync(device)) {   // solo en worker thread
   conn.write("PING".getBytes());
   byte[] resp = conn.read();
}   // se cierra por sí solo
```

## Buenas prácticas
- **Asíncrono en la UI** (`connect(...)`), **`*Sync` solo en worker**.
- Los callbacks/futures vuelven a la UI con `Component.onUI(...)`.
- `try-with-resources` en la `BluetoothConnection`.
- **Prueba en dispositivo real** por plataforma (el core es modelado; la radio vive en el launcher).

## Errores comunes
- Llamar a las variantes `*Sync` en el paint thread (bloquea la UI).
- Esperar que el **core** haga I/O de radio real (es esqueleto; el transporte es de la plataforma).
- Tocar la UI dentro de `thenAccept`/callback sin `onUI(...)`.

## Véase también
- [Sonido y utilidades de dispositivo](/docs/data-and-services/device-sound-and-misc) · [Permisos por plataforma](/docs/platforms)
