SwitchBot温湿度計値のセンサーデータをマイクロサービスで取得する

#はじめに
ifLinkマイクロサービスを開発している八教と申します。

SwitchBot温湿度計とBLE通信を行い温度湿度の値を取得するマイクロサービスを作成時、javaでのサンプル記事がなかったので、
ifLinkに関わらず参考にしていただければと思います。

#検証環境情報
開発環境：AndroidStudio 3.6
検証端末：Oppo A5 2020 Android version 10

#ifLinkとは？
https://iflink.jp/

#参考情報
SwitchBot温湿度計のオープンAPI
https://github.com/OpenWonderLabs/python-host/wiki/Meter-BLE-open-API#New_Broadcast_Message
オープンAPIを使用したPythonソースコード
https://github.com/OpenWonderLabs/python-host/blob/master/switchbot_meter_py3.py

#BLE GATTでのデータ取得概要
BLE GATTでデータを取得する際以下1～4の処理を作成することでSwitchBot温湿度計の値を取得できます。

 01．BLE通信に必要な権限を要求
 02．BLEデバイスを検出する処理
 03．BLEデバイスとの接続・切断の処理
 04．BLEデバイスから値を読み込む(変更の通知を受け取る)処理

#ifLinkマイクロサービスへの実装
#01．BLE通信に必要な権限を要求
IfLinkConnector継承クラス
　BLE通信に必要な権限取得を行います。

    private void requestPermissions() {
        if (bDBG) Log.d(TAG,"requestPermissions");
        // 取得するパーミッション一覧を設定
        String[] permlist = {
                Manifest.permission.ACCESS_COARSE_LOCATION
        };
        // PermissionActivityを呼び出し、ユーザに権限の取得を要求
        Intent intent = new Intent(SBotTemperatureHumidityIms.this, PermissionActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        String PERMISSION_KEY = "permissions";
        intent.putExtra(PERMISSION_KEY, permlist);
        String PERMISSION_IMS_CLASS_KEY = "permission_ims_class";
        intent.putExtra(PERMISSION_IMS_CLASS_KEY, this.getClass().getCanonicalName());
        String DEBUG_LOG_KEY = "debug_log";
        intent.putExtra(DEBUG_LOG_KEY, bDBG);
        startActivity(intent);
    }

#02．BLEデバイスを検出する処理
IfLinkConnector継承クラス
　BLE通信を行うデバイスのアドレスを取得します。
　アドレスはIMS設定メニューでデバイスのBDアドレスを事前に設定します。"

    @Override
    protected void updateConfigForIms() throws IfLinkAlertException {
        Log.d(TAG, "updateConfigForIms");
        IfLinkSettings config = getConfig();
        if (config == null) {
            Log.e(TAG, "updateConfigForIms config is null");
            return;
        }
        try {
            // 使用するセンサーのBLEアドレス
            SwitchBotMeterAddress = config.getStringValue(getString(R.string.pref_SwitchBotMeter_address_key), "");
            Log.d(TAG, "SwitchBotMeterAddress=" + SwitchBotMeterAddress);
        } catch (IfLinkAlertException e) {
            Log.e(TAG, "", e);
        }
        stopScan();
        scanBot();
    }

IfLinkConnector継承クラス
　BLE通信を行うデバイスの検索を行います。
　当該処理の実行タイミングはIfLinkからの起動された場合と、IMS設定メニューにてBDアドレス変更等された場合になります。

    private boolean scanBot() {
        if (bDBG) Log.d(TAG, "scanBot");

        // 端末のBluetooth設定状態を確認
        switch (checkBt()) {
            case STAT_NOT_SUPPORTED:
                if (bDBG) Log.d(TAG, "BT is not suppored");
                break;
            case STAT_DISABLED:
                if (bDBG) Log.d(TAG, "BT is suppored, but not enabled");
                break;
            case STAT_ENABLED:
                if (bDBG) Log.d(TAG, "BT is enabled");
                break;
        }
        if (mBleScanner == null) {
            Log.e(TAG, "scanBot getBluetoothLeScanner error.");
            return false;
        }
        // Manufacturerデータのフィルタ情報の作成
        byte[] mData1 = null,mData2 = null, mDataMask = null;
        try {
            mData1 = Hex.decodeHex("54".toCharArray());
            mData2 = Hex.decodeHex("74".toCharArray());
            mDataMask = Hex.decodeHex("FF".toCharArray());
        } catch (DecoderException e) {
            Log.e(TAG, e.getMessage(), e);
        }
        // 検索でSwitchBotMeterに絞り込む
        ScanFilter scanFilter1 = new ScanFilter.Builder()
                .setServiceUuid(UUID_SERVICE_PRIVATE)
                .setServiceData(UUID_SERVICE_DATA,mData1,mDataMask)
                .build();
        ScanFilter scanFilter2 = new ScanFilter.Builder()
                .setServiceUuid(UUID_SERVICE_PRIVATE)
                .setServiceData(UUID_SERVICE_DATA,mData2,mDataMask)
                .build();
        ArrayList scanFilterList = new ArrayList();
        scanFilterList.add(scanFilter1);
        scanFilterList.add(scanFilter2);
        ScanSettings scanSettings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
        // 一定時間が過ぎるとスキャンをキャンセルさせる
        final Timer timer = new Timer(false);
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                if (bDBG) Log.d(TAG, "Stop SwitchBotMeter scan...");
                mBleScanner.stopScan( mLeScanCallback );
                timer.cancel();
            }
        };
        timer.schedule(task, SCAN_PERIOD);
        if (bDBG) Log.d(TAG, "Start SwitchBotMeter scan...");
        mBleScanner.startScan(scanFilterList, scanSettings, mLeScanCallback );
        return true;
    }

IfLinkConnector継承クラス
　BLE通信可能なデバイスであるかチェックを行います。

    private int checkBt() {
        Log.d(TAG, "checkBt");
        // Bluetoothアダプタを取得する
        Log.d(TAG, "mBleAdapter1= " + mBleAdapter);
        if (mBleAdapter == null) {
            BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
            mBleAdapter = manager.getAdapter();
            Log.d(TAG, "mBleAdapter2 = " + mBleAdapter);
            // Bluetoothマネジャーから取得できない。STAT_NOT_SUPPORTED
            if (mBleAdapter == null) {
                Toast.makeText(this, R.string.bluetooth_is_not_supported, Toast.LENGTH_SHORT).show();
                Log.e(TAG, getString(R.string.bluetooth_is_not_supported));
                return STAT_NOT_SUPPORTED;
            }
        }
        // Bluetoothが使用不可の場合、STAT_DISABLED
        if (!mBleAdapter.isEnabled()) {
            if (bDBG) Log.d(TAG, "Bluetooth is not enabled");
            return STAT_DISABLED;
        }
        // BLEスキャナの取得
        if (mBleScanner == null) {
            mBleScanner = mBleAdapter.getBluetoothLeScanner();
            Log.d(TAG, "mBleScanner=" + mBleScanner);
        }
        Log.d(TAG, "Bluetooth is enabled");
        return STAT_ENABLED;
    }

IfLinkConnector継承クラス
　BLEスキャンを停止します。
　BLEスキャンのコールバック時、スキャン時の一定時間経過

    private void stopScan() {
        if (bDBG) Log.d(TAG, "stopScan");
        if (mBleScanner == null) {
            return;
        }
        // スキャンの停止
        if (bDBG) Log.d(TAG, "Stop SwitchBotMeter Sensor scan...");
        mBleScanner.stopScan(mLeScanCallback);
    }

#03．BLEデバイスとの接続・切断の処理
    DeviceConnector継承クラス
　  BLEデバイスとの接続

    private boolean connect() {
        Log.d(TAG, "connect()... mBluetoothGatt start");
        if (bDBG) Log.d(TAG, "connect()... mBluetoothGatt start");
        if( mDeviceSerial.isEmpty() ) {
            Log.e(TAG, "BLE address is empty.");
            return false;
        }
        if( null != mBluetoothGatt ) {
            Log.w(TAG, "BLE already connected.");
            return true;
        }

        // BLEデバイスと接続
        // BluetoothAdapterオブジェクトを用いて、BLEデバイスのオブジェクトを取得。
        BluetoothDevice device = mBluetoothAdapter.getRemoteDevice( mDeviceSerial );
       // 取得したBLEデバイスオブジェクトを用いて、connectGatt関数を呼び出す。
       // 第３引数には、ConnectGattコールバックオブジェクトを指定。
        mBluetoothGatt = device.connectGatt( mIms, true, mGattcallback );
        return true;
    }

#04．BLEデバイスから値を読み込む処理
DeviceConnector継承クラス
BLEにおいて値は、キャラクタリスティック（≒特性値）と呼ばれます。
BLEデバイスからキャラクタリスティックを読み込むためにすべきことは以下の通り

        private void setCharacteristicNotification( UUID uuid_service, UUID uuid_characteristic, boolean enable ) {
            Log.d(TAG, "setCharacteristicNotification");
            if (mBluetoothGatt == null) {
                return;
            }
            BluetoothGattCharacteristic characteristic = mBluetoothGatt.getService(uuid_service).getCharacteristic(uuid_characteristic);
            //取得したキャラクタリスティックオブジェクトを用いて、デスクリプターを取得します。
            if (!mBluetoothGatt.setCharacteristicNotification( characteristic, enable) ) {
                Log.e(TAG, "setCharacteristicNotification error.  uuid:" + uuid_characteristic);
            }
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID_NOTIFY);
            //取得したデスクリプターに、ノーティフィケーションを有効にする値をセットします。
            if (enable) {
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            } else {
                descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
            }
            //BluetoothGattオブジェクトを用いて、デスクリプターを書き込みます。
            if (!mBluetoothGatt.writeDescriptor(descriptor)) {
                Log.e(TAG, "writeDescriptor error.  uuid:" + uuid_characteristic);
            }
        }

Bluetooth Low Energyには、キャラクタリスティックが書き換えられて，値が変更された時にその通知を受け取れる仕組みがあります。
この仕組みをノーティファイ(Notify)と呼びます。ノーティファイを用いると値が自動更新されます。

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            if (bDBG) Log.d(TAG, "onDescriptorWrite="+ gatt);
            if (mBluetoothGatt != gatt) {
                if (bDBG) Log.d(TAG, "onDescriptorWrite: reject other gatt:"+ gatt);
                gatt.close();
                return;
            }
            if (status != BluetoothGatt.GATT_SUCCESS) {
                if (bDBG) Log.d(TAG, "[ERROR] invalid status");
                disconnect();
            }
            if (descriptor.getCharacteristic().getUuid().equals(UUID_CHARACTERISTIC_NOTIFICATION)) {
                //BluetoothGattオブジェクトを用いて、キャラクタリスティックを書き込みます。
                if (bDBG) Log.d(TAG, "writeCharacteristic");
                BluetoothGattCharacteristic blechar = mBluetoothGatt.getService(UUID_SERVICE_PRIVATE).getCharacteristic(UUID_CHARACTERISTIC_WRITE);
           // 取得したデスクリプターに、ノーティフィケーションを有効にする値をセットします。 
               blechar.setValue(COMMAND_DISPLAY);
                mBluetoothGatt.writeCharacteristic(blechar);
            }
        }
    };

BluetoothGattコールバックオブジェクトの定義にて、キャラクタリスティック変更が通知されたときの処理である
onCharacteristicChanged関数を定義

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            Log.d(TAG, "onCharacteristicChanged");
            changedCharacteristic(characteristic.getUuid(), characteristic.getValue());
        }

BLEコールバック onCharacteristicChangedメソッドが呼ばれた場合に
ソフトウェア固有の処理（値の表示等）を行います。
    private void changedCharacteristic(UUID uuid, byte[] bytes) {
        Log.d(TAG, "changedCharacteristic");

        float tempFla = bytes[1];
        tempFla = tempFla / 10;
        float temp = bytes[2] + 128;
        int Humi = bytes[3] % 128;

        if(temp < 0){ tempFla *=  -1; }

        float meterTemp = temp + tempFla;
        // 取得したデータをifLinkに送信
        sendData(meterTemp,Humi);
    }