# Bluetooth BLE / GATT

> The asynchronous BLE/GATT stack (BluetoothManager) that replaced JSR-82/SPP.

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

# Bluetooth BLE / GATT

CrossFly uses an **asynchronous BLE/GATT stack** (`dev.crossfly.device.bluetooth`), which **replaced**
the old JSR-82/SPP. The modeling is based on `CompletableFuture`.

## ⚠️ Honesty note (read before coding)
In the **core**, several operations are a **skeleton over in-memory state** — `BluetoothConnectionBuilder.build()`
creates empty streams, `GattCharacteristic.read()/write()` only touch the local cache, and `discoverServices()`
returns an empty list. The **real radio transport** comes from the platform launchers (Android/iOS). The
**modeling** (UUIDs, futures, service→characteristic→descriptor hierarchy) is already final; rely on
the API, but test on a real device per platform.

## Discovery and connection
```java
BluetoothManager bt = BluetoothManager.getInstance();   // throws IOException

// scan (asynchronous)
bt.scanForDevices(10, TimeUnit.SECONDS, new BluetoothManager.ScanCallback() {
   public void onSuccess(List<BluetoothDevice> devices) { /* choose one */ }
   public void onError(String error) { Debug.log(error); }
});

// connect (asynchronous) — returns a BluetoothConnection
bt.connect(device, new BluetoothManager.ConnectionCallback() {
   public void onSuccess(BluetoothConnection conn) { usar(conn); }
   public void onError(String error) { Debug.log(error); }
});
```
There are **`*Sync`** variants (`scanForDevicesSync`, `connectSync`) — use them **only on a worker thread**, never
on the paint thread.

## GATT — read the battery and subscribe to notifications
**service → characteristic → descriptor** model, I/O via `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 ->                     // subscribes to changes
      Component.onUI(() -> label.setText((bytes[0] & 0xFF) + "%")));
});
```
`GattCharacteristic` exposes `read()` → `CompletableFuture<byte[]>`, `write(byte[])` → `CompletableFuture<Void>`,
`enableNotifications(Consumer<byte[]>)`, and the capability checks `canRead()`/`canWrite()`/`canNotify()`.

## `BluetoothConnection` is `AutoCloseable`
```java
try (BluetoothConnection conn = bt.connectSync(device)) {   // only on a worker thread
   conn.write("PING".getBytes());
   byte[] resp = conn.read();
}   // closes on its own
```

## Best practices
- **Async on the UI** (`connect(...)`), **`*Sync` only on a worker**.
- Callbacks/futures return to the UI with `Component.onUI(...)`.
- `try-with-resources` on the `BluetoothConnection`.
- **Test on a real device** per platform (the core is modeling; the radio lives in the launcher).

## Common mistakes
- Calling `*Sync` variants on the paint thread (blocks the UI).
- Expecting the **core** to do real radio I/O (it's a skeleton; the transport belongs to the platform).
- Touching the UI inside `thenAccept`/the callback without `onUI(...)`.

## See also
- [Device sound and utilities](/docs/data-and-services/device-sound-and-misc) · [Per-platform permissions](/docs/platforms)
