# How to scan and connect to a Bluetooth device (BLE)

> Discover Bluetooth devices, connect, and read/receive notifications from GATT characteristics.

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

# How to scan and connect to a Bluetooth device (BLE)

**Purpose.** Discover nearby Bluetooth devices, **connect** to one of them, and exchange data —
by stream (Classic/SPP) or by **GATT characteristics** (BLE: read, write, receive notifications).

> ## ⚠️ Honesty note (read before using)
> In the **`core` module**, the BLE layer is a **skeleton over an in-memory cache**, not the real radio:
> - `GattCharacteristic.read()` returns the **cached value** (`CompletableFuture.completedFuture(value)`), not a hardware read.
> - `GattCharacteristic.write(byte[])` only **stores** the value locally.
> - `enableNotifications(...)` **registers** the listener, but the data is triggered by `notifyValueChanged(...)`.
> - `BluetoothDevice.discoverServices(...)` returns an **empty list** in core.
>
> The real radio (actual scan/connection/GATT) is provided by the **platform launchers**
> (Android/iOS). Treat the code below as the **public API**; the effective behavior depends on the
> launcher the application runs on.

## APIs used
- `BluetoothManager.getInstance() throws IOException` — singleton.
- `scanForDevices(long duration, TimeUnit unit, ScanCallback cb)` — asynchronous discovery; `ScanCallback.onSuccess(List<BluetoothDevice>)` / `onError(String)`.
- `connect(BluetoothDevice device, ConnectionCallback cb)` — asynchronous connection; `ConnectionCallback.onSuccess(BluetoothConnection)` / `onError(String)`.
- `BluetoothConnection` (`AutoCloseable`) — `read()` / `read(int max)` → `byte[]`; `write(byte[])` / `write(String)` → `int`.
- GATT: `GattCharacteristic.read()` → `CompletableFuture<byte[]>`; `write(byte[])` → `CompletableFuture<Void>`; `enableNotifications(Consumer<byte[]>)` → `CompletableFuture<Void>`.
- `BluetoothManager.isBluetoothEnabled()`, `getPairedDevices(...)`, `getLocalDevice()`.

## Complete example (copy, paste, and run)

> **Remember the honesty note above:** in the `core` module the BLE layer is a **skeleton over an
> in-memory cache** — the real radio comes from the platform launchers (Android/iOS). The example below is
> the **public API**; the effective behavior depends on the launcher the application runs on.

```java
package com.exemplo;

import java.util.concurrent.*;              // TimeUnit
import dev.crossfly.ui.*;                   // Window, Component, Button, Label
import dev.crossfly.ui.layout.*;            // VerticalLayout
import dev.crossfly.device.bluetooth.*;     // BluetoothManager, BluetoothDevice

/** Runnable main screen. Point the launcher (classname) and run: gradlew :lwjgl3:run */
public class MainWindow extends Window
{
   @Override
   public void onShow() throws Exception
   {
      super.onShow();
      setLayout(new VerticalLayout());

      Label resultado = new Label("");
      add(LAY_AUTO, resultado);

      Button acao = new Button("Escanear Bluetooth");
      acao.onAction(c ->
      {
         try
         {
            BluetoothManager bt = BluetoothManager.getInstance();

            // Scan (callbacks outside the paint thread → onUI when touching the UI)
            bt.scanForDevices(10, TimeUnit.SECONDS, new BluetoothManager.ScanCallback()
            {
               public void onSuccess(java.util.List<BluetoothDevice> devices)
               {
                  Component.onUI(() -> resultado.setText("Encontrados: " + devices.size()));
               }
               public void onError(String error)
               {
                  Component.onUI(() -> resultado.setText("Erro: " + error));
               }
            });
         }
         catch (java.io.IOException e)
         {
            resultado.setText("Bluetooth indisponível");   // Bluetooth system unavailable
         }
      });
      add(LAY_AUTO, acao);
   }
}
```

> The snippet below shows **only the core**. To run it, place it in the `MainWindow` skeleton above.

## Minimal snippet

```java
// verified at source: BluetoothManager.java (getInstance, scanForDevices, connect); BluetoothConnection.java (read/write); GattCharacteristic.java (read/write/enableNotifications)
import java.util.concurrent.*;             // TimeUnit
import dev.crossfly.device.bluetooth.*;    // BluetoothManager, BluetoothDevice, BluetoothConnection, GattCharacteristic
import dev.crossfly.ui.*;                  // Component (onUI)

try {
   BluetoothManager bt = BluetoothManager.getInstance();

   // 1) Scan (callbacks outside the paint thread → onUI when touching the UI)
   bt.scanForDevices(10, TimeUnit.SECONDS, new BluetoothManager.ScanCallback() {
      public void onSuccess(java.util.List<BluetoothDevice> devices) {
         Component.onUI(() -> listarDispositivos(devices));
      }
      public void onError(String error) {
         Component.onUI(() -> mostrarErro(error));
      }
   });

   // 2) Connect and exchange data over a stream (Classic/SPP)
   BluetoothDevice alvo = /* chosen by the user */;
   bt.connect(alvo, new BluetoothManager.ConnectionCallback() {
      public void onSuccess(BluetoothConnection conn) {
         try (conn) {                        // AutoCloseable
            conn.write("PING".getBytes());
            byte[] resposta = conn.read();    // up to 4096 bytes
         } catch (java.io.IOException e) { /* handle it */ }
      }
      public void onError(String error) {
         Component.onUI(() -> mostrarErro(error));
      }
   });

   // 3) BLE / GATT — read and subscribe to notifications of a characteristic
   GattCharacteristic ch = /* obtained from the device's service */;
   ch.enableNotifications(data ->
      Component.onUI(() -> atualizarValor(data)));   // Consumer<byte[]>
   ch.read().thenAccept(valor ->
      Component.onUI(() -> mostrarValor(valor)));     // CompletableFuture<byte[]>
} catch (java.io.IOException e) {
   // Bluetooth system unavailable
}
```

## Go deeper
- Bluetooth/BLE guide: [Bluetooth & BLE](/docs/data-and-services/device-bluetooth-ble)
