web-dev-qa-db-ja.com

BLE Android-onConnectionStateChangeが呼び出されていません

周辺機器に接続しようとして問題が発生しました。コールバックonConnectionStateChange(...)BluetoothDevice#connectGatt(...)の後に呼び出されない場合があります。私が達成しようとしているのは、ユーザーアクションによってトリガーされる高速で短い接続です。

この状況は、事前の特定のアクションなしで約10回に1回発生します。約20〜30秒、またはアプリケーションが強制終了されて再度開かれるまで続きます。私が従う通常の手順は次のとおりです。

  1. 周辺機器を見つけるためにデバイスをスキャンします。
  2. BluetoothDevice#connectGatt(...)を呼び出します。接続に1秒以上かかる場合、接続が「スタック」しているため接続できないため、BluetoothDevice#connectGatt(...)が再度呼び出されます。これは、5回の試行に制限されています。
  3. onConnectionStateChange(...)newState CONNECTEDを指定して呼び出され、サービスの検出を開始します。
  4. 残りの操作は問題なく実行されます。
  5. 切断後、BluetoothGatt#close()が呼び出されます。

問題はポイント3で発生します。ときどきonConnectionStateChange(...)が呼び出されません。ほとんどの場合、問題は特定の動作から始まります。 BluetoothDevice#connectGatt(...)を呼び出した後、onConnectionStateChange(...)newState CONNECTEDで呼び出されますが、その直後(〜40ミリ秒)がnewStatus DISCONNECTEDで再び呼び出されます。ステータスの変更が短時間であるため、デバイスが接続を試みず、状態をDISCONNECTEDに変更していないと推測できます。問題は次の場合に終了します。

  1. 20-30秒が経過しました。この間、onConnectionStateChange(...)は呼び出されません。問題が終了すると、onConnectionStateChange(...)が、アプリが接続を試行した回数だけ呼び出されます。たとえば、BluetoothDevice#connectGatt(...)が15回呼び出された場合、newStateがDISCONNECTEDに等しい状態でonConnectionStateChange(...)が15回呼び出されます。これらの接続のいずれにおいても、ステータスがCONNECTEDに変化しないため、これは奇妙です。
  2. アプリが強制終了され、再開されました。

このエラーは、SDK18およびSDK 21で発生します。

_@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
    String deviceName = device.getName();
    if (deviceName == null) return;
    Log.d("BLUETOOTH CONNECTION", "Device found: " + device.getName());
    if (mMode == SCAN_MODE) {
        mListener.deviceFound(device, rssi, scanRecord);
    }
    else {
        mDevices.put(device.hashCode(), device);
        stopScan();
        // Samsung devices with SDK 18 or 19 requires that connectGatt is called in main thread.
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Log.d("BLUETOOTH CONNECTION", "Executing first device.connectGatt()");
                BluetoothGatt gatt = device.connectGatt(mContext, false, mGattCallback);
                retryIfNecessary(device, gatt);
                mTryingToConnect = true;
            }
        });
    }
}
_
_private void retryIfNecessary(final BluetoothDevice device, final BluetoothGatt gatt) {
    if (isRetryLimitReached()) {
        Log.d("BLUETOOTH CONNECTION", "Try count limit reached");
        finishConnection(gatt);
        mRetryCount = 0;
        mListener.error(TIMEOUT);
        return;
    }
    mRetryCount++;
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            Log.d("BLUETOOTH CONNECTION", "Check if it is frozen.");
            if (isWorking()) {
                Log.d("BLUETOOTH CONNECTION", "Frozen, create new connection.");
                BluetoothGatt gatt = device.connectGatt(mContext, false, mGattCallback);
                retryIfNecessary(device, gatt);
            }
        }
    }, RETRY_INTERVAL_MS);
}
_
_    @Override
    public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
        Log.d("BLUETOOTH CONNECTION", "On connection state changed. Device: "+ gatt.getDevice().getAddress());
        if (!mConnected && BluetoothGatt.STATE_CONNECTED == newState) {
            Log.d("BLUETOOTH CONNECTION", "Connected");
            mTryingToConnect = false;
            mTryingToDiscoverServices = true;
            mConnected = true;
            gatt.discoverServices();
        }
        else if(BluetoothGatt.STATE_DISCONNECTED == newState) {
            Log.d("BLUETOOTH CONNECTION", "Disconnected and closing gatt.");
            mConnected = false;
            gatt.close();
            if (!mConnectionFinished && mRetryCount == 0) {
                finishConnection(gatt);
            }
        }
    }
_

IOSアプリは常にこの問題なしに接続できるので、周辺機器は関係ないと思います。

何か案は?前もって感謝します。

編集!

これ 答えは次のように言います:

直接接続の間隔は60ミリ秒、ウィンドウの間隔は30ミリ秒なので、接続ははるかに速く完了します。さらに、一度に保留できる直接接続要求は1つだけで、30秒後にタイムアウトになります。 onConnectionStateChange()は、このタイムアウトを示すために、state = 2、status = 133で呼び出されます。

したがって、この30秒の間隔で保留中の接続要求があり、2番目の30でタイムアウトします。それはありそうにありませんが、この時間を短くするために私ができることはありますか?または、接続障害の説明が表示されていない可能性があります。ありがとう。

EDIT 02/03/2016

役立つかもしれない新しい情報。問題が発生したとき(_newState=DISCONNECTED_で呼び出されてから約40ms後、onConnectionStateChange(...)が_newState=CONNECTED_で呼び出されたとき)、ステータスは62 = 0x03Eです。見る ここ そのステータスコードはGATT_CONN_FAIL_ESTABLISHを意味します。このステータスを検出すると、gatt接続を閉じていますが、問題は解決しません。また、切断して閉じてみました。アイデア?ありがとう。

19
avmatte

誰かが同様の問題を抱えている場合、問題は周辺機器(arduino)で使用されるBLEチップを変更することで最終的に解決されました。その変更の前に、私が見つけた回避策は、各接続後にBLEをオフにしてからオンにすることでした。ソリューションは完璧ではありませんでしたが、接続速度を大幅に改善しました。

5
avmatte

この質問に対する回答をまだ探しているかどうかはわかりません。個人的には、低エネルギーデバイスに対して「ユーザーアクションによってトリガーされる高速で短い接続」を作成することはお勧めしません。代わりに、connectGattメソッドでautoConnectオプションを「true」に設定できます。

device.connectGatt(mContext、true、mGattCallback); [偽の代わりに]

それが役に立てば幸い!

Android Bluetoothはたまにリサイクルする必要があります。この時間に遭遇したときにデバイスでBLEを再起動してみましたか?

これは、奇妙なことが発生したときにBLEを再起動するために使用したスニペットです。

static Handler mHandler = new Handler();
public static void restartBle() {
    final BluetoothManager mgr = (BluetoothManager) ApplicationBase.getAppContext().getSystemService(Context.BLUETOOTH_SERVICE);
    final BluetoothAdapter adp = mgr.getAdapter();
    if (null != adp) {
        if (adp.isEnabled()) {
            adp.disable();

            // TODO: display some kind of UI about restarting BLE
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (!adp.isEnabled()) {
                        adp.enable();
                    } else {
                        mHandler.postDelayed(this, 2500);
                    }
                }
            }, 2500);
        }
    }
}
3
Michael