0

BluetoothManager 是一个负责与 ble 设备交互的类。
它作为单例注入 RxBleClient 并开始扫描片段/服务的启动方法。有时扫描会自动停止,有时几秒钟后我找不到原因。

问题是设备仍在广播它的包,但我的应用程序无法接收它,因为 BluetoothAdapter 已经停止了扫描。

以下是在服务中扫描的日志:

03-09 11:17:58.526 10196-10196/com.turboegg.storm D/BluetoothManager  ***   212: Clear scan
03-09 11:17:58.527 10196-10196/com.turboegg.storm D/BluetoothManager  ***   72: Start scanning
03-09 11:17:58.579 10196-10196/com.turboegg.storm I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
03-09 11:17:58.589 10196-10196/com.turboegg.storm I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
03-09 11:17:58.602 10196-10196/com.turboegg.storm I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
03-09 11:17:58.604 10196-10196/com.turboegg.storm I/AppCompatViewInflater: app:theme is now deprecated. Please move to using android:theme instead.
03-09 11:18:01.592 10196-10196/com.turboegg.storm D/BluetoothAdapter: startLeScan(): null
03-09 11:18:01.596 10196-10196/com.turboegg.storm D/BluetoothAdapter: STATE_ON
03-09 11:18:01.600 10196-10207/com.turboegg.storm D/BluetoothLeScanner: onClientRegistered() - status=0 clientIf=8
03-09 11:18:01.656 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device C6:86:B6:35:FF:00
03-09 11:18:01.784 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device EE:AC:C7:32:9A:F6
03-09 11:18:02.643 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device C6:86:B6:35:FF:00
03-09 11:18:02.835 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device EE:AC:C7:32:9A:F6
03-09 11:18:03.659 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device C6:86:B6:35:FF:00
03-09 11:18:03.796 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device EE:AC:C7:32:9A:F6
03-09 11:18:04.663 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device C6:86:B6:35:FF:00
03-09 11:18:04.785 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device EE:AC:C7:32:9A:F6
03-09 11:18:05.648 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device C6:86:B6:35:FF:00
03-09 11:18:05.809 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device EE:AC:C7:32:9A:F6
03-09 11:18:06.817 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device EE:AC:C7:32:9A:F6
03-09 11:18:07.672 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device C6:86:B6:35:FF:00
03-09 11:18:07.827 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device EE:AC:C7:32:9A:F6
03-09 11:18:08.682 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device C6:86:B6:35:FF:00
03-09 11:18:08.826 10196-10196/com.turboegg.storm D/BluetoothManager  ***   79: Device EE:AC:C7:32:9A:F6
03-09 11:18:09.009 10196-10196/com.turboegg.storm D/BluetoothAdapter: stopLeScan()
03-09 11:18:09.011 10196-10196/com.turboegg.storm D/BluetoothAdapter: STATE_ON

这是蓝牙管理器:

public class BluetoothManager {

    private Context context;
    private RxBleClient rxBleClient;
    private StorageManager storageManager;
    private Subscription scanSubscription, connectionSubscription, notificationSubscription, writeSubscription, keepAwakenSubscription;
    private RxBleDevice connectedDevice;
    private RxBleConnection activeConnection;

    public BluetoothManager(Context context, RxBleClient rxBleClient, StorageManager storageManager) {
        this.context = context;
        this.rxBleClient = rxBleClient;
        this.storageManager = storageManager;
    }

    public void onDestroy() {
        stop();
        storageManager.setAllSensorsDisconnectible();
        storageManager.clearResources();
    }

    public void stop() {
        clearScan();
        disconnect();
    }

    public void stopScan() {
        clearScan();
    }

    public void disconnect() {
        clearConnection();
        clearNotification();
        clearWrite();
        clearKeepAwaken();
    }

    public void startScan() {
        clearScan();
        Timber.d("Start scanning");
        scanSubscription = Observable.timer(3, TimeUnit.SECONDS)
                .flatMap(ignored -> rxBleClient.scanBleDevices())
                .filter(rxBleScanResult -> CommonUtils.isStormSensor(rxBleScanResult.getBleDevice()))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(rxBleScanResult -> {
                    RxBleDevice device = rxBleScanResult.getBleDevice();
                    Timber.d("Device %s", device.getMacAddress());
                    if (device.getMacAddress() != null && !storageManager.containsDiscoveredDevice(device.getMacAddress())) {
                        Timber.d("Found device, %s %s", device.getName(), device.getMacAddress());
                        storageManager.addDiscoveredSensor(device.getMacAddress(), device.getName());
                        EventBus.getDefault().post(new DeviceDiscovered(device.getMacAddress(), device.getName()));
                    }
                    storageManager.setSensorConnectible(device.getMacAddress(), Calendar.getInstance().getTime());
                }, this::onBleFailure);
    }

    public void connectDevice(String macAddress, String outInDoor) {
        disconnect();
        connectedDevice = rxBleClient.getBleDevice(macAddress);
        if (connectedDevice != null) {
            Timber.d("Start connecting to %s", macAddress);
            connectionSubscription = connectedDevice.establishConnection(context, false)
                    .subscribe(connection -> {
                        // Need to delay write operations due to sensor processor limitations
                        writeCharacteristic(connection, CommonUtils.getBleTime())
                                .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
                        writeCharacteristic(connection, AppConstants.BLUETOOTH_SET_BUZZER_OFF)
                                .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
                        writeCharacteristic(connection, AppConstants.BLUETOOTH_SET_MASK)
                                .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
                        writeCharacteristic(connection, AppConstants.BLUETOOTH_CLEAR_STATS)
                                .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
                        writeCharacteristic(connection, AppConstants.BLUETOOTH_CLEAR_MEM)
                                .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
                        writeCharacteristic(connection, outInDoor)
                                .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
                        writeCharacteristic(connection, AppConstants.BLUETOOTH_CLEAR_MASK)
                                .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
                        writeCharacteristic(connection, AppConstants.BLUETOOTH_SET_MASK)
                                .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS);
                        activeConnection = connection;
                        getBasicParams(connection, macAddress);
                        subscribeForNotifications(connection, macAddress);
                    }, throwable -> {
                        Timber.e("Error while connecting " + throwable.toString());
                        handleConnectionError(macAddress);
                        disconnect();
                    });
        }
    }

    public void writeOutInDoor(String outInDoor) {
        clearWrite();
        writeSubscription = Observable.combineLatest(
                writeCharacteristic(activeConnection, AppConstants.BLUETOOTH_CLEAR_STATS)
                        .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
                writeCharacteristic(activeConnection, AppConstants.BLUETOOTH_CLEAR_MEM)
                        .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
                writeCharacteristic(activeConnection, outInDoor)
                        .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
                writeCharacteristic(activeConnection, AppConstants.BLUETOOTH_CLEAR_MASK)
                        .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
                writeCharacteristic(activeConnection, AppConstants.BLUETOOTH_SET_MASK)
                        .delay(AppConstants.BLUETOOTH_WRITE_DELAY_MILIS, TimeUnit.MILLISECONDS),
                (bytes, bytes2, value, bytes4, bytes5) -> value)
                .subscribe(value -> Timber.d("Written value %s", new String(value)),
                        throwable -> Timber.e("Error writing outInDoor: %s", throwable.toString())
                );
    }

    public void handleDeviceStatus(boolean checked) {
        if (checked) {
            keepDeviceAwaken();
        } else {
            clearKeepAwaken();
        }
    }

    public void handleDeviceDisconnection(String macAddress) {
        if (connectedDevice != null && connectedDevice.getMacAddress().equals(macAddress)) {
            disconnect();
        }
    }

    /**
     * Private methods
     */

    private void getBasicParams(RxBleConnection connection, String macAddress) {
        Observable.combineLatest(
                connection.readCharacteristic(AppConstants.BLUETOOTH_UUID_BATTERY),
                connection.readCharacteristic(AppConstants.BLUETOOTH_UUID_FIRMWARE),
                (bytes, bytes2) -> new DeviceConnected(bytes, bytes2, macAddress))
                .take(1)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(deviceConnected -> {
                    Timber.d("Bat %s", deviceConnected.getBattery());
                    Timber.d("Fmw %s", deviceConnected.getFirmware());
                    storageManager.updateSensorInfo(macAddress, deviceConnected.getBattery(), deviceConnected.getFirmware());
                    storageManager.setSensorConnected(macAddress);
                    handleDeviceStatus(storageManager.getStormSensor(macAddress).isStatusChecked());
                    EventBus.getDefault().post(deviceConnected);
                }, throwable -> Timber.e("Sensor basic info error %s", throwable.getMessage()));

    }

    private void subscribeForNotifications(RxBleConnection connection, String macAddress) {
        notificationSubscription = Observable.combineLatest(
                connection.setupNotification(AppConstants.BLUETOOTH_UUID_EVENT)
                        .<byte[]>flatMap(observable -> observable),
                connection.setupNotification(AppConstants.BLUETOOTH_UUID_DISTANCE)
                        .<byte[]>flatMap(observable -> observable),
                NotificationStormEvent::new)
                .filter(CommonUtils::isStormEvent)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(notificationStormEvent -> {
                    Timber.d("New storm, event: %s, distance %s km", notificationStormEvent.getEvent(), notificationStormEvent.getDistance());
                    storageManager.addStormEvent(Calendar.getInstance().getTime(), notificationStormEvent.getDistance());
                    EventBus.getDefault().post(notificationStormEvent);
                }, throwable -> Timber.e("Notification error %s", throwable.toString()));
    }

    private void keepDeviceAwaken() {
        clearKeepAwaken();
        keepAwakenSubscription = Observable.interval(
                AppConstants.BLUETOOTH_KEEP_AWAKE_MINS, AppConstants.BLUETOOTH_KEEP_AWAKE_MINS, TimeUnit.MINUTES)
                .flatMap(aLong -> writeCharacteristic(activeConnection, CommonUtils.getBleTime()))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(bytes -> Timber.d("Written value %s to keep device awaken", new String(bytes)),
                        throwable -> Timber.e("Error keeping awake %s", throwable.toString()));
    }

    private void onBleFailure(Throwable throwable) {
        if (throwable instanceof BleScanException) {
            handleBleScanException((BleScanException) throwable);
        }
    }

    private void clearScan() {
        Timber.d("Clear scan");
        if (scanSubscription != null) {
            scanSubscription.unsubscribe();
            Timber.d("Clear unsubscribed");
        }
        scanSubscription = null;
    }

    private void clearConnection() {
        if (connectionSubscription != null) connectionSubscription.unsubscribe();
        connectionSubscription = null;
        connectedDevice = null;
        activeConnection = null;
    }

    private void clearNotification() {
        if (notificationSubscription != null) notificationSubscription.unsubscribe();
        notificationSubscription = null;
    }

    private void clearWrite() {
        if (writeSubscription != null) writeSubscription.unsubscribe();
        writeSubscription = null;
    }

    private void clearKeepAwaken() {
        if (keepAwakenSubscription != null) keepAwakenSubscription.unsubscribe();
        keepAwakenSubscription = null;
    }

    private Observable<byte[]> writeCharacteristic(RxBleConnection connection, String value) {
        byte[] bytesToWrite = value.getBytes(StandardCharsets.UTF_8);
        return connection.writeCharacteristic(AppConstants.BLUETOOTH_UUID_UART_WRITE, bytesToWrite);
    }

    private void handleBleScanException(BleScanException bleScanException) {
        switch (bleScanException.getReason()) {
            case BleScanException.BLUETOOTH_NOT_AVAILABLE:
                Timber.e("Bluetooth is not available");
                break;
            case BleScanException.BLUETOOTH_DISABLED:
                Timber.e("Enable bluetooth and try again");
                break;
            case BleScanException.LOCATION_PERMISSION_MISSING:
                Timber.e("On Android 6.0 location permission is required. Implement Runtime Permissions ");
                break;
            case BleScanException.LOCATION_SERVICES_DISABLED:
                Timber.e("Location services needs to be enabled on Android 6.0");
                break;
            case BleScanException.BLUETOOTH_CANNOT_START:
            default:
                Timber.e("Unable to onStart scanning");
                break;
        }
    }

    private void handleConnectionError(String macAddress) {
        // This is the case where e.g. connection was lost or the device was turned off
        // Need to call for realm to modify the sensor object in different thread
        Realm realm = Realm.getDefaultInstance();
        realm.executeTransaction(realm1 -> {
            StormSensor sensor = realm1.where(StormSensor.class).equalTo(AppConstants.STORM_SENSOR_MAC_ADDRESS, macAddress).findFirst();
            sensor.setConnected(false);
        });
        realm.close();
    }
}
4

0 回答 0