Android BLE подключается сразу к нескольким устройствам

Я видел множество вопросов о том, как целенаправленно подключаться к нескольким устройствам. Но в моей ситуации я пытаюсь подключиться только к одному аппаратному устройству.

У меня есть два аппаратных устройства, которые должны делать то же самое. Когда они подключаются к моему приложению через BLE, у них есть светодиод, который горит сплошным цветом. Все это отлично работает, когда у меня включено только одно устройство. Однако когда я включаю два устройства, а затем пытаюсь подключиться только к одному. Оба светодиода устройств загорятся. Хотя, похоже, я не получаю никаких входящих данных от того, к которому не собирался подключаться.

Не думаю, что виноват аппарат. Потому что у меня нет этой проблемы на iOS. Я думаю, телефон может где-то запоминать ранее подключенные устройства?

Извините, это много кода. Но я считаю важным иметь весь этот класс. Любая помощь горячо приветствуется.

package com.roberts.croberts.orange;

import android.annotation.TargetApi;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanCallback;

import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import android.bluetooth.le.BluetoothLeScanner;

import java.util.ArrayList;
import java.util.List;


@TargetApi(21)
public class BluetoothRegulator {

    private static BluetoothRegulator instance = null;

    private Context context;
    private BluetoothLeScanner mLEScanner;

    private BluetoothDevice orangeDevice;
    //scanner stuff
    private Handler mHandler;

    // Stops scanning after 3 seconds.
    private static final long SCAN_PERIOD = 3000;

    //connected stuff
    private android.bluetooth.BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;
    private BluetoothGatt mBluetoothGatt;

    public ArrayList<BluetoothDevice> devices = new ArrayList<>();

    private Handler foundHandler = new Handler();
    private Handler servicesHandler = new Handler();

    private ScanCallback mScanCallback;

    public static BluetoothRegulator sharedInstance(){
        if(instance == null) {
            instance = new BluetoothRegulator();
            Log.i("chase", "created new instance");
        }
        return instance;
    }
    // Implements callback methods for GATT events that the app cares about.  For example,
    // connection change and services discovered.
    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                mBluetoothGatt.discoverServices();
                Log.i(TAG, "BR: onconnectionsStateChanged Connected to GATT server.");
                // Attempts to discover services after successful connection.
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.i(TAG, "Disconnected from GATT server.");
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {

            if (status == BluetoothGatt.GATT_SUCCESS) {
                // Loops through available GATT Serviceokay so ees.
                for (BluetoothGattService gattService : mBluetoothGatt.getServices()) {
                    for (BluetoothGattCharacteristic gattCharacteristic : gattService.getCharacteristics()) {
                        mBluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
                        Log.i(TAG, mBluetoothGatt == null ? "mbluetooth is null" : "should be subscribed");
                    }
                }
                Log.i("chase", "did connect and discover devices");
            } else {
                Log.w(TAG, "Not Success onServicesDiscovered received: " + status);
                connect(orangeDevice);
            }
        }
        private Object getFieldFromObject(Object obj, String name){
            try {
                Field field = obj.getClass().getDeclaredField(name);
                field.setAccessible(true);
                return field.get(obj);

            }catch(Exception e){
                Log.i("chase", "e: "+e);
                return null;
            }
        }
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            Log.i("BR: chase", "received data!");
        }
    };


    public void close() {
        if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.close();
        mBluetoothGatt = null;
    }

    public boolean initialize(android.bluetooth.BluetoothManager btmanager, Context ctx) {
        mBluetoothManager = btmanager;
        context = ctx;
        mBluetoothAdapter = mBluetoothManager.getAdapter();
        if (mBluetoothAdapter == null) {
            Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
            return false;
        }

        if (Build.VERSION.SDK_INT >= 21) {
            mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
            setUpCallBack();
        }
        return true;
    }

    public void scan() { //we call scan when they hit the connect button...
        // Stops scanning after a pre-defined scan period.
        Log.i("chase", "start scanning");
        devices = new ArrayList<>();
        if (mHandler == null) mHandler = new Handler();
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                if (Build.VERSION.SDK_INT < 21) {
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                } else {
                    mLEScanner.stopScan(mScanCallback);
                }
            }
        }, SCAN_PERIOD);

        if (Build.VERSION.SDK_INT < 21) {
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mLEScanner.startScan(mScanCallback);
        }
    }
    private void foundDevice(BluetoothDevice device){
        final String deviceName = device.getName();

        if (deviceName != null && deviceName.length() > 5 && (deviceName.substring(0, 6).equals("orange") || deviceName.substring(0, 6).equals("smartb"))) {
            for (BluetoothDevice d : devices){
                if (d.getAddress().equals(device.getAddress())){
                    return;
                }
            }
            mHandler.removeCallbacksAndMessages(null);
            devices.add(device);
            if (devices.size() == 1) { //wait one second and then assume there aren't any more devices named "orange"
                foundHandler.postDelayed(new Runnable() {
                    public void run() {
                        doneSearching();
                    }
                }, 1000);
            }
        }
    }
    private void doneSearching(){
        if (Build.VERSION.SDK_INT < 21) {
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        } else {
            mLEScanner.stopScan(mScanCallback);
        }
        if (devices.size() == 1){
            BluetoothDevice device = devices.get(0);
            connect(device);
        }else{
            //normally this displays a list and the user can choose which device. But this works just as well for now.
            BluetoothDevice device = devices.get(0);
            connect(device);
        }
    }
    //connect method
    public boolean connect(BluetoothDevice btdevice) {
        orangeDevice = btdevice;
        if (mBluetoothAdapter == null || btdevice == null || btdevice.getAddress() == null) {
            return false;
        }
        if (Build.VERSION.SDK_INT < 21) {
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        } else {
            mLEScanner.stopScan(mScanCallback);
        }
        devices = new ArrayList<>();
        mBluetoothGatt = orangeDevice.connectGatt(context, true, mGattCallback);
        return true;
    }

    public void disconnect() {
        if (mBluetoothAdapter == null || mBluetoothGatt == null) return;
        mBluetoothGatt.disconnect();
        mBluetoothGatt = null;
    }

    // Device scan callback.
    private BluetoothAdapter.LeScanCallback mLeScanCallback =
        new BluetoothAdapter.LeScanCallback() {
            @Override
            public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
                foundDevice(device);
            }
        };

    public void setUpCallBack(){
        mScanCallback = new ScanCallback() {
            @Override
            public void onScanResult(int callbackType, ScanResult result) {
                BluetoothDevice device = result.getDevice();
                foundDevice(device);
            }
            @Override
            public void onScanFailed(int errorCode) {
                Log.e("Chase", "Scan Failed Error Code: " + errorCode);
            }
        };
    }
}

Обновлять

Я играл с планшетом galaxy и не смог воссоздать проблему. Так что я думаю, это зависит от устройства. Проблема возникает на Galaxy S3, и я пытаюсь найти другие устройства для тестирования.

Кроме того, мне удалось заполучить несколько новых устройств, и кажется, что если устройство никогда раньше не было подключено к телефону (первичное устройство), то это устройство не путается и не думает, что оно подключено, когда это не так. Таким образом, мы увидим его при поиске, но он никогда не думает, что я подключаюсь к нему, пока я действительно не подключусь к нему. После этого в половине случаев он думает, что я пытаюсь с ним поговорить, хотя это не так. Я надеюсь, что в этом есть смысл. Это подтверждает теорию о том, что телефон каким-то образом кэширует старые устройства. Я попытался удалить приложение и переустановить его, чтобы посмотреть, будет ли оно иметь тот же эффект, что и использование обычного устройства, но, похоже, приложение не имеет к этому никакого отношения. Устройство все равно будет подключаться (когда это не должно быть) после того, как оно было представлено на телефоне, даже если я установил приложение заново.


person Chase Roberts    schedule 12.12.2015    source источник
comment
Поскольку вы вызываете метод завершенного поиска внутри обработчика с задержкой в ​​1000 мс, может быть вероятность, что когда этот метод будет вызван, ваш список устройств будет изменен на другой размер, и он пропускает if (devices.size () == 1) и падает в else и соединяет оба устройства. Поэтому, пожалуйста, проверьте размер устройств arraylist   -  person Gautam    schedule 12.12.2015
comment
Готум, я не слежу. doneSearching () вызывается только после того, как устройство было найдено, и вызывается только один раз (поэтому он должен подключать только одно устройство). Я не вижу условий гонки или чего-то подобного.   -  person Chase Roberts    schedule 12.12.2015


Ответы (2)


Я бы проверил сами устройства BLE. Есть ли шанс, что у них может быть один и тот же системный идентификатор? Я считаю, что это первая характеристика 0x180A. Если так - хосту будет сложно их различить и может произойти такое двойное соединение.

person DrivingSimulatorGuy    schedule 12.12.2015
comment
Также проверьте наличие дублированного MAC-адреса / аппаратного адреса, хотя обычно это тоже сбивает с толку iOS. - person Chris Stratton; 12.12.2015
comment
Я не думаю, что у них одинаковый аппаратный адрес. У устройств есть уникальные UUID (я предполагаю, что это единственный аппаратный адрес). & Я отправил эту ветку специалисту по аппаратному обеспечению для подтверждения. Но, @DrivingSimulatorGuy, я немного запутался, что вы имеете в виду под первой характеристикой в ​​0x180A. Вы можете уточнить? - person Chase Roberts; 12.12.2015
comment
UUID не являются аппаратным адресом. Допустим, у вас есть 2 отдельных устройства BLE, которые имеют один и тот же сервис - скажем, акселерометр. У них будут одинаковые UUID для этих сервисов. Более того, одно устройство BLE часто имеет много UUID для адресации служб внутри него. Аппаратный адрес или иногда люди называют его MAC - это уникальный номер устройства BLE. (вызов MAC не совсем правильный, так как это 8-байтовое число, а MAC - 6-байтовое число) - person DrivingSimulatorGuy; 13.12.2015
comment
Системный идентификатор - это уникальный 8-байтовый номер для каждого радиочипа BT. В зависимости от программного стека, используемого в прошивке этих устройств BLE, этот номер может быть перепрограммирован, поэтому, например, если вы загрузите один и тот же двоичный код на оба датчика, он может сделать их системный идентификатор одинаковым, хотя большинство систем, которые я видел, не будут работать что. - person DrivingSimulatorGuy; 13.12.2015
comment
Каждый сервер GATT, который является частью устройства BLE, имеет несколько сервисов, которые всегда доступны. Т.е. 0x1800, 0x1801 и 0x180A. Затем - перейдите к службам, адресуемым через UUID. Взгляните на эту таблицу служб GATT в качестве примера и найдите там идентификатор системы: Сервер TI SensorTag GATT - person DrivingSimulatorGuy; 13.12.2015

-Предположим, у вас 2 устройства. Итак, вызывается foundDevice (). Теперь в арсенале устройств содержится 1 устройство.

-После этого вы используете обработчик, который вызывает doneSearching () и проверяет

if device.size()==1

Он возвращает истину, и вы вызываете connect ()

-Внутри подключения вы снова создаете Arraylist, т.е.

devices = new ArrayList<>();

Итак, что происходит сейчас, ваши устройства ArrayList ‹> () содержат 0 элементов.

-Так что теперь, когда второе устройство снова найдено, вышеуказанные шаги повторяются, потому что всякий раз, когда вызывается метод подключения, размер списка обновляется до 0, поэтому просто удалите строку

 devices = new ArrayList<>();

внутри метода connect ()

person Gautam    schedule 12.12.2015
comment
Это могло быть проблемой. Но проблема не в этом. Каждый раз, когда я запускал код, устройство содержало два элемента при вызове doneSearching (). Более того, doneSearching вызывает stopScan (), поэтому после этого новые устройства не обнаруживаются. - person Chase Roberts; 12.12.2015