表題は「AndroidからBluetoothで接続」となっていますが、実際にはSerial Port Profile(以下SPP)での接続になるので、特にOSに依存しないです。

Bluetoothで接続するときは、接続先のUUIDが必要になり、SPPにおいてサーバ(接続を受け付ける側:この場合はUnlimited Hand)のUUIDは固定になります。

備考
使用するプロファイルSPP(Serial Port Profile)-
接続先UUID00001101-0000-1000-8000-00805F9B34FBSPPへの接続なので接続先UUIDは固定
コマンドのデリミタ0x0A(LF)送信するコマンドデータの終端を示す値
機器名称RNBT-[\\w]{4}機器名称の正規表現
機器のバージョンアップにより変更になる可能性あり

ここまでは、Android/iOS/Windowsなど、各種OSからUnlimited Handへ接続する際に必要となる情報となります。

ここからが、実際にAndroidから接続するときに、実装例を記載する予定でしたが、勢い余ってライブラリ化(Bluetooth接続ヘルパUnlimitedHand接続ヘルパ)(リンク先はブログ作成時の最新タグ)してしまったため、それらを元に説明します(いつか、もうちょっとシンプルなものを用意してみます)。

  1. スマートフォンとUnlimited Handのペアリング
  2. Bluetoothはアプリからはセキュリティ的に勝手にペアリングはできないようなので、一度ペアリングを実施する必要があります。
    • Unlimited Handの電源をON
    • 赤丸のボタンを3秒程度、押すと振動が発生し、ランプが点灯します。

    • スマートフォンからUnlimited Handを検索(以下の例はAndroid 7.0が動作しているNexus 5X)
    • スマートフォン上にて「設定」アプリ - 「Bluetooth」を選択し、まずはBluetoothをONに設定(赤丸のスイッチをONに変更)。

      「使用可能なでバス」の一覧に「RNBT-XXXX」(例ではRNBT-C60C)が見つからないときは、右上のメニューから「更新」を選択してみるか、暫く待つ。

    • 「RNBT-XXXX」が見つかったら、それを選択すると、ペアリングの許可を確認するダイアログが表示されるので、「ペア設定する」ボタンを選択。」

    • ペアリングが完了すると、「ペアされたデバイス」欄に「RNBT-XXXX」が移動されている。この状態でペアリングが完了となり、Unlimited Handの電源がON且つスマートフォンのBluethoothがON且つ両機器が十分に近い距離に存在するときにアプリから接続可能になる。

  3. アプリからBluetoothを起動
  4. まずはBluetoothが起動しているか否かの確認を実施する必要があります。Bluetooth接続ヘルパのBluetoothAccessHelper.startBluetoothHelperにて以下の処理にてBluetoothの状態を確認しています。
    public synchronized void startBluetoothHelper() {
        if (sAdapter == null) {
            sAdapter = BluetoothAdapter.getDefaultAdapter();
        }
    
        if (sAdapter == null) {
            // device is not support bluetooth
            if (mStatusListener != null) {
                changeStatus(mStatusListener, StatusNoSupportBluetooth, null);
            }
        } else {
            if (!mStartHelper) {
                mStartHelper = true;
                LocalBroadcastManager.getInstance(mContext).registerReceiver(mLocalBroadcastReceiver, new IntentFilter(LaunchBluetooth));
    
                if (sAdapter.isEnabled()) {
                    addBluetoothAccessHelper(this);
                    if (mStatusListener != null) {
                        changeStatus(mStatusListener, StatusStartBluetooth, null);
                    }
                } else {
                    enableBluetooth(null);
                }
            }
        }
    }
        
    このメソッドの先頭で「BluetoothAdapter.getDefaultAdapter()」を実施していますが、これでBluetoothAdapterインスタンスを取得できないときは、端末としてBluetoothをサポートしていないときなので、処理を中断させています。
    BluetoothAdapterインスタンスを取得できたとき、BluetoothAdapter.isEnable()をコールして、使用可能か否かの判定を実施し、使用不可能(falseが返却された)であれば、Bluetoothの起動(BluetoothAccessHelper.enableBluetooth)を実施します。

    BluetoothAccessHelper.enableBluetoothでは大きく分けて
    • ユーザの同意なしに自動的にBluetoothをONにする
    • ユーザの同意によりBluetoothをONにする
    の2パターンの実装を組み込んでいます。ただし、Googleとしてはユーザの同意を得てBluetoothをONにすることを推奨しているので、現状は後者の動作をするように設定しています。
    前者は「BluetoothAdapter.enable()」をコールすることで実現できます。後者はAction種別:BluetoothAdapter.ACTION_REQUEST_ENABLEのIntentを送信することでOS側でBluetoothのONへの切り替え確認ダイアログを表示し、その結果を「Activity.onActivityResult」のresultCode(Activity.RESULT_OK:起動、Activity.RESULT_CANCELED:キャンセル)にて返却してくれます。
    ※ 今回紹介しているコードはライブラリ化した一環でActivity以外からも使用されることを想定しているため、HandleResultActivityという別のActivityを経由してAction種別:BluetoothAdapter.ACTION_REQUEST_ENABLEのIntentを送信し、結果をLocalBroadcastにて返却されせています。
    private void enableBluetooth(OnBluetoothStatusListener listener) {
        if (sAdapter != null && !sAdapter.isEnabled()) {
            if (sEnableAutoStartBluetooth) {
                if (sAdapter.enable()) {
                    // notify change to all listener for start bluetooth
                    changeStatus(StatusStartBluetooth, null);
                }
            } else {
                if (sBluetoothStatus == StatusInit) {
                    enableBluetoothWithConfirmation();
                    changeStatus(StatusProgress, null);
                } else if (sBluetoothStatus == StatusProgress) {
                    if (listener != null) {
                        listener.onStatusChange(sBluetoothStatus, sScanMode);
                    }
                } else if (sBluetoothStatus == StatusStartBluetooth) {
                    // bluetooth is enabled at once, but user changes disabled
                    enableBluetoothWithConfirmation();
                    changeStatus(StatusProgress, null);
                }
            }
        }
    }
        
    private void enableBluetoothWithConfirmation() {
        Intent launchIntent = new Intent();
        launchIntent.setClass(mContext, HandleResultActivity.class);
        launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    
        Intent transferIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        launchIntent.putExtra(HandleResultActivity.INTENT_PARCELABLE_EXTRA_TRANSFER_INTENT, transferIntent);
        launchIntent.putExtra(HandleResultActivity.INTENT_INT_EXTRA_HOWTO_CALLBACK, HandleResultActivity.CALLBACK_BY_LOCAL_BROADCAST);
        launchIntent.putExtra(HandleResultActivity.INTENT_STRING_EXTRA_CALLBACK_ACTION, LaunchBluetooth);
        launchIntent.putExtra(HandleResultActivity.INTENT_INT_EXTRA_REQUEST_CODE, LaunchBluetoothInt);
    
        mContext.startActivity(launchIntent);
    }
        

  5. アプリからUnlimited HandへSPPを用いて接続
  6. こちらに記載されている情報に従って、接続を行います。
    1. Unlimited Hand用BluetoothDeviceインスタンスを取得
    2. まず、先にペアリングを実施しているので、OS側でBluetoothDeviceインスタンスが登録されているはずなので、UnlimitedHand接続ヘルパにて探して取得。
      やっていることは「BluetoothAccessHelper.getPairedDevices()」(内部でBluetoothAdapter.getBondedDevices()を実行しているだけ)で取得したペアリング済みのデバイス一覧から、それぞれの名前を確認し、指定された名称(正規表現でRNBT-[\\w]{4})のものを探しています。
      public List getDevices(String deviceName, boolean useRegExp) {
          ArrayList ret = new ArrayList();
          Set deviceSet = mBTAccessHelper.getPairedDevices();
          BluetoothDevice[] deviceArray = deviceSet != null ? deviceSet.toArray(new BluetoothDevice[deviceSet.size()]) : new BluetoothDevice[0];
          Method compareMethod = null;
      
          try {
              if (useRegExp) {
                  compareMethod = String.class.getMethod("matches", String.class);
              } else {
                  compareMethod = Object.class.getMethod("equals", Object.class);
              }
          } catch (NoSuchMethodException e) {
              LogUtil.exception(TAG, e);
          }
      
          for (BluetoothDevice device : deviceArray) {
              try {
                  if ((boolean) compareMethod.invoke(device.getName(), deviceName)) {
                      ret.add(device);
                      break;
                  }
              } catch (IllegalAccessException e) {
                  LogUtil.exception(TAG, e);
              } catch (InvocationTargetException e) {
                  LogUtil.exception(TAG, e);
              }
          }
      
          return ret;
      }
              
    3. BluetoothDeviceインスタンスを使用してSPPへ接続し、BluetoothSocketインスタンスを取得
    4. 見つかったBluetoothDeviceインスタンスと接続先UUID"00001101-0000-1000-8000-00805F9B34FB"を「BluetoothAccessHelper.connect()」に指定することで、BluetoothSocketの生成を試みます。
      ※ BluetoothDeviceインスタンスはペアリングさえしていれば、Bluetooth機器の電源がOFFや圏外に居ても取得可能です。
      public boolean connect(BluetoothDevice device, UUID targetUuid) {
          boolean ret = false;
          DataBox dataBox = new DataBox(targetUuid, device, new byte[0], 0, 0);
          BluetoothSocket clientSocket = getClientSocket(dataBox);
      
          return clientSocket != null;
      }
              
      private BluetoothSocket getClientSocket(DataBox dataBox) {
          BluetoothSocket clientSocket = null;
          HashMap connectedSocketMap = null;
          synchronized (mConnectedSocketMap) {
              connectedSocketMap = mConnectedSocketMap.get(dataBox.mDevice);
          }
          if (connectedSocketMap != null) {
              synchronized (connectedSocketMap) {
                  clientSocket = connectedSocketMap.get(dataBox.mTargetUUID);
              }
          }
      
          try {
              if (clientSocket == null || !clientSocket.isConnected()) {
                  if (clientSocket != null) {
                      try {
                          clientSocket.close();
                      } catch (IOException e) {
                      }
                  }
      
                  // create connection socket
                  int trialCount = 0;
                  Exception lastException = null;
                  while (trialCount <= MaxConnectionRetryCount) {
                      try {
                          clientSocket = dataBox.mDevice.createRfcommSocketToServiceRecord(dataBox.mTargetUUID);
                          clientSocket.connect();
      
                          if (connectedSocketMap == null) {
                              connectedSocketMap = new HashMap();
                              synchronized (mConnectedSocketMap) {
                                  mConnectedSocketMap.put(dataBox.mDevice, connectedSocketMap);
                              }
                          }
      
                          synchronized (connectedSocketMap) {
                              connectedSocketMap.put(dataBox.mTargetUUID, clientSocket);
                          }
      
                          LogUtil.d(TAG, "connection established");
                          break;
                      } catch (IOException e) {
                          lastException = e;
                          clientSocket = null;
                          Thread.sleep(ConnectionRetryIntervalMS);
                          trialCount++;
                      }
                  }
      
                  if (lastException != null) {
                      LogUtil.e(TAG, lastException.getLocalizedMessage());
                  }
              } else {
                  LogUtil.d(TAG, "reuse connection");
              }
          } catch (Exception e) {
              LogUtil.e(TAG, e.getLocalizedMessage());
          }
      
          return clientSocket;
      }
              
      BluetoothAccessHelper.getClientSocket()でいろいろやっていますが、最も大切なことは、指定されたBluetoothDeviceインスタンスに対して「BluetoothDevice.createRfcommSocketToServiceRecord()」(UUIDも指定されたものを設定)を実行し、BluetoothSocketインスタンスを取得し、そのBluetoothSocketインスタンスに対して「BluetoothSocket.connect()」を実行することとなります。
      ※ 他の処理はBluetoothSocketの再利用処理となります。

コメントの投稿