Android: переключение между Bluetooth-устройствами SPP

У меня есть два разных Bluetooth-принтера. Bixolon SPP-R200 и Fujitsu FTP-628WSL110. Я могу подключиться к каждому из них отдельно (используя Samsung Galaxy SII), распечатать, отключить и снова подключиться. Однако, если я выключаю Bixolon и пытаюсь выполнить сопряжение с Fujitsu (ранее не сопряженный, Bixolon все еще сопряжен), то при попытке подключиться к созданному сокету происходит сбой. То же самое наоборот.

Вот сообщение об ошибке:

07-02 13:00:11.040: E/MyApp.BluetoothConnection(9380): Failed to connect to rfcomm socket.
07-02 13:00:11.040: E/MyApp.BluetoothConnection(9380): java.io.IOException: Service discovery failed
07-02 13:00:11.040: E/MyApp.BluetoothConnection(9380):  at android.bluetooth.BluetoothSocket$SdpHelper.doSdp(BluetoothSocket.java:406)
07-02 13:00:11.040: E/MyApp.BluetoothConnection(9380):  at android.bluetooth.BluetoothSocket.connect(BluetoothSocket.java:217)
07-02 13:00:11.040: E/MyApp.BluetoothConnection(9380):  at MyApp.BluetoothConnection.connect(BluetoothConnection.java:171)
07-02 13:00:11.040: E/MyApp.BluetoothConnection(9380):  at MyApp.AbstractBluetoothPrinter.connect(AbstractBluetoothPrinter.java:34)

Вот код, который делает попытку подключения, строка, которая не работает при объясненных обстоятельствах, это btSocket.connect(); - исключение см. выше:

/** Is set in connect() */
private BluetoothSocket btSocket = null;
/** Is set prior to connect() */
private BluetoothSocket btDevice;

public boolean connect(){

        try {
            btSocket = btDevice.createRfcommSocketToServiceRecord("00001101-0000-1000-8000-00805F9B34FB");
            if (btDevice.getName().startsWith("FTP")) {
                //Special treatment for the fujitsu printer
                SystemClock.sleep(1000);
            }
        } catch (Throwable e) {
            LogCat.e(TAG, "Failed to create rfcomm socket.", e);
            return false;
        }

        try {
            // Stop Bluetooth discovery if it's going on
            BluetoothHandler.cancelDiscovery();
            // This fails under the described circumstances
            btSocket.connect();
        } catch (Throwable e) {
            LogCat.e(TAG, "Failed to connect to rfcomm socket.", e);
            return false;
        }

        // Obtain streams etc...
}

Я использую один и тот же UUID для подключения к обоим устройствам (но одновременно включается только одно устройство, они никогда не включаются одновременно), известный SPP UUID из SDK API:

00001101-0000-1000-8000-00805F9B34FB

Что заставляет меня задаться вопросом: может быть, мне нужен другой UUID для каждого устройства? Если да, то какие?


person AgentKnopf    schedule 02.07.2012    source источник


Ответы (1)


Хорошо, после нескольких дней пробных различных решений теперь я могу переключаться между вышеупомянутыми принтерами. Поскольку я не совсем уверен, какая из моих мер была причиной успеха, я перечислю их все, чтобы у тех, кто наткнется на этот пост, были некоторые подсказки о том, как исправить свои проблемы с Bluetooth. Однако я совершенно уверен в одном: вам не нужны разные UUID для подключения двух разных принтеров - вы можете использовать один и тот же UUID (но у меня всегда включен только один из них).

Я кэширую устройство, на которое последний раз печаталось, однако, в отличие от того, что было раньше, я больше не кэширую фактическое BluetoothDevice, вместо этого я кэширую только его mac-адрес, который можно получить через:

BluetoothDevice bluetoothDevice; 

//Obtain BluetoothDevice by looking through paired devices or starting discovery

bluetoothDevice.getAddress(); 

getAddress() возвращает строку: аппаратный адрес устройства. Я кэширую этот mac-адрес, и в следующий раз, когда пользователь хочет напечатать, я сопоставляю кэшированный mac-адрес с mac-адресами всех сопряженных принтеров — если mac-адрес совпадает с одним из них, я пытаюсь подключиться к этому принтеру. Если это не удается, я сбрасываю свой кэшированный MAC-адрес и пытаюсь найти другое устройство, сначала проверяя свои сопряженные устройства, может ли одно из них подключиться (если я могу успешно подключиться, я соответствующим образом обновляю свой кэшированный MAC-адрес), и если это не удается, я запускаю Bluetooth Discovery ищет другие потенциальные устройства.

Теперь, чтобы не оставлять открытыми какие-либо соединения сокетов с одним из моих принтеров, моя процедура выглядит следующим образом (я не буду использовать try-catches, которые я обернул вокруг каждого вызова, чтобы облегчить чтение):

Создать сокет

BluetoothSocket btSocket = btDevice.createRfcommSocketToServiceRecord(MY_UUID);

MY_UUID относится к хорошо известному UUID, используемому для подключения к SPP-устройствам:

00001101-0000-1000-8000-00805F9B34FB

Если создание сокета терпит неудачу (что случается редко, и если это происходит, то, скорее всего, из-за недостаточных разрешений или отключенного/недоступного bluetooth), мы не можем двигаться дальше, так как нам нужен сокет для подключения. Следовательно, в вашем блоке catch вы должны активировать метод разъединения (подробнее об этом позже).

Подключиться к созданному сокету

bSocket.connect();

Если соединение не удалось, мы не можем двигаться дальше, так как нам нужно действительное соединение сокета для получения входных и выходных потоков. Следовательно, в вашем блоке catch вы должны активировать метод разъединения (подробнее об этом позже).

Получить поток ввода и вывода

Следующим шагом будет получение входных и выходных потоков из сокета. Я делаю это в цикле for, который запускается пару раз (5 раз должно быть достаточно) - на каждой итерации я проверяю, есть ли у меня выходной поток, если нет, я пытаюсь его получить, то же самое для входного потока. В конце цикла я проверяю, есть ли у меня оба потока, если да, я выхожу из цикла (И весь метод подключения), если нет, я продолжаю цикл и пробую снова. Обычно я получаю оба потока в первой итерации цикла, однако иногда мне нужно две или три итерации, чтобы получить оба потока.

Если я дойду до кода, который следует после объявления цикла, я, очевидно, не получил свои потоки или что-то еще пошло не так. В этот момент соединение считается неудачным, и я выполняю свой код отключения (который очищает открытые потоки и сокеты, подробнее об этом позже).

Чтение/запись

Теперь, когда у вас есть подключение к целевому устройству Bluetooth, вы можете выполнять операции чтения и записи. Когда вы закончите, вы должны очистить все потоки и сокеты, подробнее об этом в следующем абзаце: Отключение. Помните: если во время операций чтения/записи возникает исключение, обязательно активируйте метод отключения, чтобы очистить ваши ресурсы. Если вашему принтеру требуется какая-либо команда инициализации, обязательно отправьте ее сразу после подключения к принтеру и перед выполнением операций чтения/записи.

Отключение

Обычно есть два случая, когда вы должны отключиться:

  • Как только вы закончите операции чтения/записи
  • Если где-то по пути произошло исключение, чтобы очистить ваши ресурсы

Закройте свои потоки

Первое, что вы хотите сделать, это очистить свои потоки, проверить оба, ваш входной и выходной поток, если они не равны нулю, закрыть их и установить для них значение null. Обязательно завершите каждую операцию (закрытие входного потока, закрытие выходного потока и т. д.) в свой собственный try-catch, так как в противном случае неспособность выполнить одну очистку (из-за возбуждения исключения) пропустит все другие меры очистки.

Закрыть сокет

Теперь, когда вы убедились, что ваши входные потоки очищены, перейдите к закрытию соединения сокета и после этого установите для него значение null.

Еще одна вещь: у меня есть Thread.sleep в начале и в конце моего метода отключения. Тот, что в начале, длится около 2,5 секунд (= 2500 миллисекунд), цель состоит в том, чтобы убедиться, что с принтером больше ничего не происходит (например, ожидающие операции чтения/записи или принтер все еще печатает и т. д.). Второй Thread.sleep находится в конце моего метода разъединения и длится около 800 миллисекунд. Причина этого сна в конце связана с проблемами, которые у меня были, когда я пытался сразу же открыть новый сокет сразу после его закрытия. Дополнительные сведения см. в этом ответе.

Вопросы?

Если у кого-то есть вопросы, связанные с моим OP или моим ответом, дайте мне знать в комментариях, и я постараюсь ответить на них.

person AgentKnopf    schedule 06.07.2012
comment
Не могли бы вы проверить этот вопрос? :D - person Skizo-ozᴉʞS; 25.02.2016
comment
@Skizo Я сделал, и похоже, что ответ здесь именно то, что вам нужно? Если это не работает для вас, пожалуйста, дайте мне знать, в чем проблема. - person AgentKnopf; 26.02.2016
comment
Я использую этот код, это более или менее пример документов Android repoно все равно пишет, что не подключен - person Skizo-ozᴉʞS; 26.02.2016
comment
@Skizo: ссылка не работает xD. Вы пытались прочитать приведенный здесь ответ и соответствующим образом изменить свой код? Кроме того, вы проверяли лог-файлы? Обязательно обратите внимание на исключения/причины того, почему он не работает. - person AgentKnopf; 27.02.2016