Bluetooth BLE / GATT
O CrossFly usa uma pilha BLE/GATT assíncrona (dev.crossfly.device.bluetooth), que substituiu
o antigo JSR-82/SPP. A modelagem é baseada em CompletableFuture.
⚠️ Nota de honestidade (leia antes de codar)
No core, várias operações são esqueleto sobre estado em memória — BluetoothConnectionBuilder.build()
cria streams vazios, GattCharacteristic.read()/write() só tocam o cache local, e discoverServices()
retorna lista vazia. O transporte de rádio real vem dos launchers de plataforma (Android/iOS). A
modelagem (UUIDs, futures, hierarquia service→characteristic→descriptor) já é definitiva; conte com
a API, mas teste em dispositivo real por plataforma.
Descoberta e conexão
BluetoothManager bt = BluetoothManager.getInstance(); // throws IOException
// escanear (assíncrono)
bt.scanForDevices(10, TimeUnit.SECONDS, new BluetoothManager.ScanCallback() {
public void onSuccess(List<BluetoothDevice> devices) { /* escolher um */ }
public void onError(String error) { Debug.log(error); }
});
// conectar (assíncrono) — retorna uma BluetoothConnection
bt.connect(device, new BluetoothManager.ConnectionCallback() {
public void onSuccess(BluetoothConnection conn) { usar(conn); }
public void onError(String error) { Debug.log(error); }
});
Há variantes *Sync (scanForDevicesSync, connectSync) — use apenas em worker thread, nunca
na paint thread.
GATT — ler bateria e assinar notificações
Modelo service → characteristic → descriptor, I/O via CompletableFuture:
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 -> // assina mudanças
Component.onUI(() -> label.setText((bytes[0] & 0xFF) + "%")));
});
GattCharacteristic expõe read() → CompletableFuture<byte[]>, write(byte[]) → CompletableFuture<Void>,
enableNotifications(Consumer<byte[]>), e os testes de capacidade canRead()/canWrite()/canNotify().
BluetoothConnection é AutoCloseable
try (BluetoothConnection conn = bt.connectSync(device)) { // só em worker thread
conn.write("PING".getBytes());
byte[] resp = conn.read();
} // fecha sozinho
Boas práticas
- Assíncrono na UI (
connect(...)),*Syncsó em worker. - Callbacks/futures voltam à UI com
Component.onUI(...). try-with-resourcesnaBluetoothConnection.- Teste em dispositivo real por plataforma (o core é modelagem; o rádio vive no launcher).
Erros comuns
- Chamar variantes
*Syncna paint thread (bloqueia a UI). - Esperar que o core faça I/O de rádio real (ele é esqueleto; o transporte é da plataforma).
- Tocar a UI dentro do
thenAccept/callback semonUI(...).