BLE-Gerät wird nach dem Trennen der Verbindung nicht erkannt – erscheint erst nach mehreren erneuten Scan-Versuchen (AndAndroid

Forum für diejenigen, die für Android programmieren
Anonymous
 BLE-Gerät wird nach dem Trennen der Verbindung nicht erkannt – erscheint erst nach mehreren erneuten Scan-Versuchen (And

Post by Anonymous »

Ich baue einen BLE-Adapter für ein medizinisches Gerät. Es scannt, stellt eine Verbindung her, richtet Benachrichtigungen ein und fordert Daten an. Alles funktioniert, wenn der Adapter das Gerät zum ersten Mal findet und eine Verbindung mit ihm herstellt. Wenn das Gerät jedoch die Verbindung trennt (absichtlich oder versehentlich), wird das Gerät bei nachfolgenden Scans häufig nicht gefunden. Nach ein paar erneuten App-Öffnungen oder ausreichend Zeit und erneuten Scanversuchen wird sie schließlich angezeigt und stellt die Verbindung wieder her.
Vollständige Klasse (sensible Werte geschwärzt):

Code: Select all

public class ThermometerAdapter implements BleDeviceManager.DeviceAdapter {

private static final String TAG = "ThermoMeterAdapter";
private static final String DEVICE_NAME = "My Thermometer";
private static final int DEFAULT_TIMEOUT_SEC = 20;
private static final int SCAN_RETRY_MAX = 5;

// GATT UUIDs
private static final UUID SERVICE_UUID = UUID.fromString("");
private static final UUID CHAR_UUID = UUID.fromString("");
//    private static final UUID CCCD_UUID = UUID.fromString("");
// Data frame constants
private static final byte START_CODE = (byte) 0xaa;
private static final byte TYPE_EAR = 0x22;
private static final byte TYPE_FOREHEAD = 0x33;
private static final byte TYPE_OBJECT = 0x55;

// Retry policy
private static final long RESCAN_DELAY_MS = 2500;
private static final long KEEPALIVE_INTERVAL_MS = 30000; // 30 seconds

private final Handler main = new Handler(Looper.getMainLooper());
private Context appCtx;
private InternalCallback cb;
private BleDeviceManager.VitalType requestedType;
private volatile boolean shouldRun;
private volatile boolean isConnected;
private long attempts;

private BluetoothAdapter btAdapter;
private BluetoothGatt gatt;
private BluetoothDevice targetDevice;
private ScanCallback scanCallback;

@Override
public void start(Context ctx, BleDeviceManager.VitalType type, InternalCallback callback) {
stop();
this.appCtx = ctx.getApplicationContext();
this.cb = callback;
this.requestedType = type;
this.shouldRun = true;
this.isConnected = false;
this.attempts = 0;

postStatus("Initializing thermometer…");
main.postDelayed(this::startScan, 300);
}

@RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN})
@Override
public void stop() {
shouldRun = false;
isConnected = false;
main.removeCallbacksAndMessages(null);

// Stop scanning first
try {
if (btAdapter != null && scanCallback != null) {
BluetoothLeScanner scanner = btAdapter.getBluetoothLeScanner();
if (scanner != null) {
scanner.stopScan(scanCallback);
Log.d(TAG, "Scanner stopped");
}
}
} catch (Throwable t) {
Log.w(TAG, "stop scanner", t);
}

// Then disconnect GATT
try {
if (gatt != null) {
gatt.disconnect();
// Small delay before close to ensure disconnect completes
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
gatt.close();
gatt = null;
Log.d(TAG, "GATT closed");
}
} catch (Throwable t) {
Log.w(TAG, "stop gatt", t);
}

targetDevice = null;
scanCallback = null;

Log.d(TAG, "Adapter stopped completely");
}

// ===== Scanning =====

private void startScan() {
if (!shouldRun) return;
if (attempts >= SCAN_RETRY_MAX) {
postError("Failed to find thermometer after " + SCAN_RETRY_MAX + " attempts.");
Log.d(TAG,"Failed to find thermometer after " + SCAN_RETRY_MAX + " attempts.");
return;
}

attempts++;
postStatus("Scanning for thermometer (" + attempts + ")…");
Log.d(TAG,"Scanning for thermometer ("  + attempts + ")…");

try {
btAdapter = BluetoothAdapter.getDefaultAdapter();
if (btAdapter == null) {
postError("Bluetooth adapter not available");
Log.d(TAG,"Bluetooth adapter not available");
return;
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!hasPermission(Manifest.permission.BLUETOOTH_SCAN)) {
postError("BLUETOOTH_SCAN permission denied");
Log.d(TAG,"BLUETOOTH_SCAN permission denied");
return;
}
}

BluetoothLeScanner scanner = btAdapter.getBluetoothLeScanner();
if (scanner == null) {
postError("BLE scanner not available");
scheduleRetry();
return;
}

scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
if (!shouldRun) return;

String name = result.getDevice().getName();
if (name != null && name.trim().equalsIgnoreCase(DEVICE_NAME.trim())) {
Log.d(TAG, "Found " + DEVICE_NAME);
targetDevice = result.getDevice();
postStatus("Found " + DEVICE_NAME + " — connecting…");

// Stop scanning before connecting
try {
scanner.stopScan(this);
} catch (SecurityException e) {
Log.w(TAG, "Failed to stop scan", e);
}

connectToDevice();
}
}

@Override
public void onScanFailed(int errorCode) {
if (!shouldRun) return;
postStatus("Scan failed (code: " + errorCode + "). Retrying…");
scheduleRetry();
}
};
scanner.startScan(scanCallback);

// Timeout for scan
main.postDelayed(() -> {
if (shouldRun && !isConnected && targetDevice == null && scanCallback != null) {
try {
scanner.stopScan(scanCallback);
} catch (Exception ignored) {}
postStatus("No devices found.  Retrying…");
scheduleRetry();
}
}, DEFAULT_TIMEOUT_SEC * 1000L);

} catch (SecurityException e) {
Log.e(TAG, "startScan: permission issue", e);
postError("Bluetooth permission denied: " + e.getMessage());
} catch (Exception e) {
Log.e(TAG, "startScan: error", e);
postError("Scan error: " + e.getMessage());
scheduleRetry();
}
}

// ===== Connection & GATT =====

private void connectToDevice() {
if (!shouldRun || targetDevice == null) return;

try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!hasPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
postError("BLUETOOTH_CONNECT permission denied");
return;
}
}

// Ensure any previous GATT is closed
if (gatt != null) {
Log.d(TAG, "Closing previous GATT before new connection");
try {
gatt.disconnect();
gatt.close();
} catch (Exception e) {
Log.w(TAG, "Error closing previous GATT", e);
}
gatt = null;
}
postStatus("Connecting…");
gatt = targetDevice.connectGatt(appCtx, false, gattCallback);

} catch (SecurityException e) {
Log.e(TAG, "connectToDevice: permission issue", e);
postError("Connection permission denied");
} catch (Exception e) {
Log.e(TAG, "connectToDevice: error", e);
postError("Connection failed: " + e.getMessage());
scheduleRetry();
}
}

private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (!shouldRun) return;

if (newState == BluetoothProfile.STATE_CONNECTED) {
isConnected = true;
postStatus("Connected. Discovering services…");
// Request high priority connection for better stability
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
gatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
} catch (Exception e) {
Log.w(TAG, "Failed to request connection priority", e);
}
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!hasPermission(Manifest.permission.BLUETOOTH_CONNECT)) return;
}
// Small delay before service discovery for stability
main.postDelayed(() -> {
if (gatt != null && shouldRun) {
gatt.discoverServices();
}
}, 300);
} catch (SecurityException e) {
Log.e(TAG, "onConnectionStateChange: permission", e);
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
postStatus("Disconnected.");
closeGatt();
if (shouldRun) scheduleRetry();
}
}

@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (!shouldRun) return;

if (status != BluetoothGatt.GATT_SUCCESS) {
postError("Service discovery failed (status: " + status + ")");
closeGatt();
scheduleRetry();
return;
}

postStatus("Services discovered.  Enabling notifications…");
enableNotifications(gatt);
}

@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
byte[] value) {
if (!shouldRun) return;

Log.d(TAG, "onCharacteristicChanged: " + bytesToHex(value));
handleTempData(value);
}

@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
int status) {
if (!shouldRun) return;

if (status != BluetoothGatt.GATT_SUCCESS) {
Log.w(TAG, "onCharacteristicWrite failed: " + status);
}
}

@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
if (!shouldRun) return;

if (status == BluetoothGatt.GATT_SUCCESS) {
postStatus("Waiting for temperature reading…");
//                startKeepalive();
} else {
postError("Failed to enable notifications (status: " + status + ")");
closeGatt();
scheduleRetry();
}
}
};

private void enableNotifications(BluetoothGatt gatt) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (!hasPermission(Manifest.permission.BLUETOOTH_CONNECT)) return;
}

BluetoothGattService service = gatt.getService(SERVICE_UUID);
if (service == null) {
postError("Service 0xFFF0 not found");
closeGatt();
scheduleRetry();
return;
}

BluetoothGattCharacteristic characteristic = service.getCharacteristic(CHAR_UUID);
if (characteristic == null) {
postError("Characteristic 0xFFF3 not found");
closeGatt();
scheduleRetry();
return;
}

// Enable notifications
gatt.setCharacteristicNotification(characteristic, true);

BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")  // CCCD
);
if (descriptor != null) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}

} catch (SecurityException e) {
Log.e(TAG, "enableNotifications: permission", e);
} catch (Exception e) {
Log.e(TAG, "enableNotifications: error", e);
postError("Notification setup failed: " + e.getMessage());
closeGatt();
scheduleRetry();
}
}
//    private void startKeepalive() {
//        // Cancel any existing keepalive
//        if (keepaliveRunnable != null) {
//            main.removeCallbacks(keepaliveRunnable);
//        }
//
//        keepaliveRunnable = new Runnable() {
//            @Override
//            public void run() {
//                if (shouldRun && isConnected &&  gatt != null) {
//                    try {
//                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
//                            if (!hasPermission(Manifest.permission.BLUETOOTH_CONNECT)) {
//                                return;
//                            }
//                        }
//
//                        // Read RSSI as a lightweight keepalive operation
//                        boolean readStarted = gatt.readRemoteRssi();
//                        if (!readStarted) {
//                            Log.w(TAG, "Keepalive: readRemoteRssi returned false");
//                        }
//                    } catch (SecurityException e) {
//                        Log.w(TAG, "Keepalive: permission denied", e);
//                    } catch (Exception e) {
//                        Log.w(TAG, "Keepalive: error", e);
//                    }
//
//                    // Schedule next keepalive
//                    main.postDelayed(this, KEEPALIVE_INTERVAL_MS);
//                }
//            }
//        };
//
//        // Start first keepalive
//        main.postDelayed(keepaliveRunnable, KEEPALIVE_INTERVAL_MS);
//        Log.d(TAG, "Keepalive started (interval: " + (KEEPALIVE_INTERVAL_MS / 1000) + "s)");
//    }
// ===== Temperature Data Parsing =====

private void handleTempData(byte[] data) {
if (data == null || data.length < 5) {
Log.w(TAG, "Invalid data length: " + (data != null ? data.length : 0));
return;
}

try {
// Parse frame
byte startCode = data[0];
byte typeCode = data[1];
int tempRaw = ((data[2] & 0xFF)

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post