2015-05-16 4 views
8

Muszę przesłać niektóre dane z mojej aplikacji na system Android przez Bluetooth (do Arduino). Nie czytam/nie odbieram niczego z Arduino. W przypadku moich potrzeb z pojedynczym gwintem wybrałem IntentService. Po sparowaniu mój kod działa poprawnie po raz pierwszy, łącząc i wysyłając dane. Rozłączam się po wysłaniu danych bez błędów. Ale gdy próbuję połączyć drugi raz dalej, pojawia się następujący błąd, gdy próbuję myBluetoothSocket.connect():Problemy z połączeniem Bluetooth z IntentService

odczytu nie powiodła się, gniazdo mogą zamkniętych lub limitu czasu, przeczytaj ret: -1

Jedynym rozwiązaniem jest wyłączenie urządzenia Arduino i ponowne połączenie (nie pomoże to, gdy wymuszam zatrzymanie aplikacji i spróbuję ponownie się połączyć).

Zauważ, że wszystko działa poprawnie, gdy pojawia się 2 wątki (jeden do odczytu i zapisu), niezależnie od tego, ile razy łączę się i wysyła dane (udowadniając, że nie ma nic złego po stronie Arduino, "powstrzymując" starą połączenie).

Oto mój kod Android:

import android.app.IntentService; 
import android.bluetooth.BluetoothAdapter; 
import android.bluetooth.BluetoothDevice; 
import android.bluetooth.BluetoothSocket; 
import android.content.Intent; 
import android.content.Context; 
import android.os.Build; 
import android.os.ParcelUuid; 
import android.widget.Toast; 

import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.lang.reflect.Method; 
import java.util.UUID; 

public class DataTransmissionService extends IntentService { 

    private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); 
    private static final String TAG = "DataTransmissionService"; 

    private BluetoothAdapter btAdapter = null; 
    private BluetoothSocket btSocket = null; 
    private OutputStream outStream = null; 
    private BluetoothDevice device = null; 

    public DataTransmissionService() { 
     super("DataTransmissionService"); 
    } 

    @Override 
    protected void onHandleIntent(Intent intent) { 
     cleanup(); 
     if (intent != null){ 

      btAdapter = BluetoothAdapter.getDefaultAdapter(); 
      pairedDeviceAddress = "already_paired_device_mac_addr"; 

      try { 
       log.d(TAG, pairedDeviceAddress); 
       device = btAdapter.getRemoteDevice(pairedDeviceAddress); 
       log.d(TAG, "Device bond state : " + device.getBondState()); 

      } catch (Exception e) { 
       log.e(TAG, "Invalid address: " + e.getMessage()); 
       return; 
      } 

      try { 
       btSocket = createBluetoothSocket(device); 
      } catch (IOException e) { 
       log.e(TAG, "Socket creation failed: " + e.getMessage()); 
       return; 
      } 

      try { 

       if (!btSocket.isConnected()) { 
        btSocket.connect();  
        log.d(TAG, "Connected"); 
       } else { 
        log.d(TAG, "Already Connected"); //flow never reaches here for any use case 
       } 

      } catch (IOException e) { 
       log.e(TAG, "btSocket.connect() failed : " + e.getMessage()); 
       return; 
      } 

      try { 
       outStream = btSocket.getOutputStream(); 
      } catch (IOException e) { 
       log.e(TAG, "Failed to get output stream:" + e.getMessage()); 
       return; 
      } 

      sendData("test"); 
      //cleanup(); called in onDestroy() 

     } 

    } 

    @Override 
    public void onDestroy(){ 
     cleanup(); 
     //notify ui 
     super.onDestroy(); 
    } 

    private void cleanup(){ 

     try { 
      if (outStream != null) { 
       outStream.close(); 
       outStream = null; 
      } 
     } catch (Exception e) { 
      log.e(TAG, "Failed to close output stream : " + e.getMessage()); 
     } 

     try { 
      if (btSocket != null) { 
       btSocket.close(); 
       btSocket = null; 
      } 
     }catch (Exception e) { 
      log.e(TAG, "Failed to close connection : " + e.getMessage()); 
     } 

    } 

    private BluetoothSocket createBluetoothSocket(BluetoothDevice device) throws IOException { 
     /*if(Build.VERSION.SDK_INT >= 10){ 
      try { 
       final Method m = device.getClass().getMethod("createInsecureRfcommSocketToServiceRecord", new Class[] { UUID.class }); 
       return (BluetoothSocket) m.invoke(device, MY_UUID); 
      } catch (Exception e) { 
       log.e(TAG, "Could not create Insecure RFComm Connection",e); 
      } 
     }*/ 

     return device.createRfcommSocketToServiceRecord(MY_UUID); 
    } 

    private void sendData(String message) { 

     byte[] msgBuffer = message.getBytes(); 
     log.d(TAG, "Sending : " + message); 
     try { 
      outStream.write(msgBuffer); 
     } catch (IOException e) { 
      log.e(TAG, "failed to write " + message); 
     } 
    } 
} 

Ja testowałem na Nexusie 5 i urządzeniach Samsung S5 (z systemem 5.1 i 5.0, odpowiednio).

+0

mam wrażenie, problem leży w przy użyciu 'IntentService' zamiast regularnego' Service' i/lub zwyczaj 'wątek '. Z "IntentService" doc: * usługa jest uruchamiana w razie potrzeby, obsługuje każdy cel po kolei za pomocą wątku roboczego i zatrzymuje się, gdy zabraknie mu pracy. * Prawdopodobnie spowoduje to spustoszenie, jeśli próbujesz utrzymać BT połączenie przez intencje. – pathfinderelite

+0

Dokładnie tego potrzebuję. Połącz, wyślij dane i zakończ połączenie. Następne połączenie prawdopodobnie nastąpi po bardzo długim okresie, tworząc nową usługę. Utrzymywanie wątków wydaje się być kłopotliwe w przypadku prostego użycia takiego jak ten. Zapewnia to również automatyczne leczenie jednego urządzenia na raz (część mojego przypadku użycia). – SlowAndSteady

+0

To, co mówisz o Arduino, nie "powstrzymywanie" połączenia wydaje się rozsądne, ale fakt, że musisz ponownie włączyć Arduino, aby móc połączyć się ponownie, sprawia, że ​​zastanawiam się. Może Arduino jakoś przechodzi w inny stan, jeśli czytasz i piszesz w tym samym wątku? (Wiem, brzmi to dziwnie.) Co się stanie, jeśli połączysz się z Arduino jednym telefonem, rozłączysz, a następnie spróbujesz połączyć się z drugim telefonem? Czy masz jakiś sposób, aby sprawdzić stan modemu bluetooth Arduino, aby sprawdzić, czy jest inaczej po pierwszym rozłączeniu, w porównaniu do wcześniejszego? – hBrent

Odpowiedz

0

Nie jestem pewien, dlaczego to działa, ale to podejście wreszcie pracował:

private BluetoothSocket createBluetoothSocket(BluetoothDevice bluetoothDevice) throws IOException { 

     try { 
      Method m = bluetoothDevice.getClass().getMethod(
        "createRfcommSocket", new Class[] { int.class }); 
      btSocket = (BluetoothSocket) m.invoke(bluetoothDevice, 1); 
     } catch (Exception e) { 
      e.printStackTrace(); 
     } 

     return btSocket; 
    } 
2

Przy próbie połączenia po raz drugi trzeba ponownie utworzyć odpowiednie gniazdo.

Należy również wziąć pod uwagę, że Arduino jest powolną platformą, może występować pewne znaczne opóźnienie między zamknięciem połączenia a ponownym otwarciem.

+0

Kod, który napisałem, tworzy nowe gniazdo za każdym razem. Nie zgadzasz się? – SlowAndSteady

-1

Metoda onDestroy() jest wywoływana tylko podczas działania Kosza ze śmieciami. Musisz zadzwonić pod numer cleanup() z onHandleIntent(Intent), tak jak przedtem, inaczej gniazdo pozostanie otwarte przez czas nieokreślony. Ponieważ zostało otwarte, nie możesz połączyć się ponownie.

Stos Bluetooth systemu Android wydaje się być niezależny od cyklu życia aplikacji: gniazdo pozostanie otwarte, nawet jeśli wymusisz zatrzymanie aplikacji. W bieżącym scenariuszu, aby zamknąć gniazdo, włącz blokadę Bluetooth w Ustawieniach.