Search code examples
androidbluetoothwiimoteouya

Android wiimote socket fails to connect


I'm trying to connect a wiimote to my OUYA (running Android 4.1.2). I've managed to get wiimotes to connect using other apps like Bluez-IME, so I know it is possible, but I'd like to be able to do it myself

This is what I have so far:

private void bloothoothConnect(final BluetoothAdapter mBluetoothAdapter){

    // Register the BroadcastReceiver
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
    filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    registerReceiver(new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            // When discovery finds a device
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                // Get the BluetoothDevice object from the Intent
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);               
                if(isWiiMote(device)){
                    mBluetoothAdapter.cancelDiscovery();
                    connectWiiMote(device);                     
                }
            }
        }
    }, filter);

    mBluetoothAdapter.startDiscovery();
}

private boolean isWiiMote(BluetoothDevice device){
    String name = device.getName();                 
    return name.equalsIgnoreCase("Nintendo RVL-CNT-01");        
}

private void connectWiiMote(final BluetoothDevice device){
    final int tickInterval = 30;
    WiimoteRunnable nWiiMote = new WiimoteRunnable(device, tickInterval){

        @Override
        protected void onButtonDown() { // TODO                         
        }};

    Thread nThread = new Thread(nWiiMote);
    nThread.start();    
}

private abstract class WiimoteRunnable implements Runnable{

    private BluetoothSocket socket;
    private long tickInterval;
    private byte[] buffer = new byte[1024];

    WiimoteRunnable(BluetoothDevice device, long tickInterval) {
        socket = createL2CAPBluetoothSocket(device);
        this.tickInterval = tickInterval;           
    }

    @SuppressLint("NewApi")
    @Override
    public void run() {

        try {
            if(!socket.isConnected()){
                // blocks here!
                socket.connect();
            }
            InputStream iStream = socket.getInputStream();

            while(socket.isConnected()){

                iStream.read(buffer);

                // do stuff with byte buffer here

                Thread.sleep(tickInterval);                                 
            }

        } catch (IOException e2) {
            closeSocket();
        } catch (InterruptedException e) {
            closeSocket();
            return;
        }

    }

    private void closeSocket(){         
        try {
            socket.close();
        } catch (IOException e) {
            return;
        }
    }

    // to be implemented later
    protected abstract void onButtonDown();

}


// see https://stackoverflow.com/questions/14761570/connecting-to-a-bluetooth-hid-device-mouse-using-l2cap

private static final int TYPE_L2CAP = 3;

/**
 * Create a BluetoothSocket using L2CAP protocol
 * Useful for HID Bluetooth devices
 * @param BluetoothDevice
 * @return BluetoothSocket
 */
@SuppressLint("NewApi")
private static BluetoothSocket createL2CAPBluetoothSocket(BluetoothDevice device){
  int type        = TYPE_L2CAP; // L2CAP protocol
  int fd          = -1;         // Create a new socket
  boolean auth    = false;      // No authentication
  boolean encrypt = false;      // Not encrypted
  int port        = 0;          // port to use (useless if UUID is given)

  ParcelUuid[] uuid = device.getUuids(); // Bluetooth UUID service
    if(uuid==null){
        if(!device.fetchUuidsWithSdp()){
            return null;
        } else {
            uuid = device.getUuids();
        }
    }


  try {
    Constructor<BluetoothSocket> constructor = BluetoothSocket.class.getDeclaredConstructor(
      int.class, int.class, boolean.class, boolean.class,
      BluetoothDevice.class, int.class, ParcelUuid.class);
    constructor.setAccessible(true);
    BluetoothSocket clientSocket = (BluetoothSocket) constructor.newInstance(
      type, fd, auth, encrypt, device, port, uuid[0]);
    return clientSocket;
  } catch (Exception e) {
    e.printStackTrace();
    return null;
  }
}

The problem is when I get to socket.connect() it blocks and never returns. The socket object is there and in the debugger I can see all the data for it.

I'm getting the socket as per Connecting to a bluetooth HID device (mouse) using L2CAP


Solution

  • Instead of using the UUID for the L2CAP socket you need to use the channel number (or port number in your code). To connect to wiimote you need to open sockets separately for control (commands from your app to wiimote) and data (input from wiimote to your app) channels.

    private static final int CONTROL_CHANNEL = 0x11;
    private static final int DATA_CHANNEL = 0x13;
    
    private BluetoothSocket controlSocket;
    private BluetoothSocket dataSocket;
    
    private OutputStream os;
    private InputStream is;
    
    private static BluetoothSocket createL2CAPBluetoothSocket(BluetoothDevice device, final int channel) {
        int type = TYPE_L2CAP; // L2CAP protocol
        int fd = -1; // Create a new socket
        boolean auth = false; // No authentication
        boolean encrypt = false; // Not encrypted
    
        try {
            Constructor<BluetoothSocket> constructor = BluetoothSocket.class.getDeclaredConstructor(int.class,
                    int.class, boolean.class, boolean.class, BluetoothDevice.class, int.class, ParcelUuid.class);
            constructor.setAccessible(true);
            BluetoothSocket clientSocket = (BluetoothSocket) constructor.newInstance(type, fd, auth, encrypt, device,
                    channel, null);
            return clientSocket;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    void connect(BluetoothDevice device) {
        try {
            controlSocket = createL2CAPBluetoothSocket(device, CONTROL_CHANNEL);        
            controlSocket.connect();
            os = controlSocket.getOutputStream();
    
            dataSocket = createL2CAPBluetoothSocket(device, DATA_CHANNEL);
            dataSocket.connect();
            is = dataSocket.getInputStream();      
    
            // open transmit & receive threads for input and output streams appropriately
    
        } catch (Exception e) {
            e.printStackTrace();
        }
    }