/ REACT

안드로이드 BLE 통신

1. 블루투스 권한 설정

안드로이드에서 BLE 통신을 사용하기 위해서는

APP 이 특정 권한들을 획득해야 한다.

아래와 같이 AndroidManifast.xml 파일에 권한 코드를 추가한다.

  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
  <uses-permission android:name="android.permission.BLUETOOTH"/>
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

BLE 통신을 위해 BLUETOOTH, BLUETOOTH_ADMIN 권한 외에

ACCESS_COARSE_LOCATION라는 위치 권한도 필요하다.

BLE 장치가 위치 정보를 제공하는 것이 많기 때문에

구글에서 위치 권한도 요청하기로 했다고 한다.

안드로이드 마시멜로 버전 이상부터는

사용자에게 요청하여 권한을 획득해야 한다.

  // OS 버전이 마시멜로 이상이라면 위치 권한이 있는지 점검 후 없다면 사용자에게 위치 권한을 요청
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      
      // 위치 권한이 있는지 점검
      // 없다면 -1 반환
      int permission = checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION);

      // 권한이 없을 때
      if (permission == PackageManager.PERMISSION_DENIED)
      {
          String[] permissions = new String[1];
          permissions[0] = android.Manifest.permission.ACCESS_COARSE_LOCATION; // 사용자에게 요청할 권한
          requestPermissions(permissions, LOCATION_PERMISSION_REQUEST_CODE); // 사용자에게 권한 요청
      }
      // 권한이 있을 때
      else
      {
          // 변수 값을 true 로 변경
          isPermissionAllowed = true;
      }
  }

권한을 요청한 후 사용자의 응답에 따라

처리가 가능하도록 메서드를 추가한다.

본 예제는 권한을 득하면 바로 주변의

BLE 기기를 스캐닝 하도록 구성되어 있다.

  // 사용자에게 권한을 요청한 결과가 들어오는 메소드
  @Override
  public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
      // 위치 권한 요청의 응답값인지 체크
      if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
          // 권한을 획득
          if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
          {
              // BLE 스캐닝
              scanLeDevice(true);
          }
          // 권한 획득 실패
          else
          {
              Toast.makeText(this, R.string.error_permission_denied, Toast.LENGTH_SHORT);
              finish();
          }
      }
  }

2. BLE SCAN

BLE 스캐닝 시 배터리 소모가 극심하기 때문에

로직을 구현할 때 두가지 권고사항이 있다.

1.원하는 기기 발견 시, 스캐닝 중지

2.스캐닝을 루프시키지 말 것.

위의 두 가지 사항에 따라 설정한 시간이

지난 후 스캐닝 후 중지되도록 구현해야 한다.

  private void scanLeDevice(final boolean enable) {
      if (enable) {
          // SCAN_PERIOD 값만큼 시간이 지나면 스캐닝 중지
          mHandler.postDelayed(new Runnable() {
              @Override
              public void run() {
                  mScanning = false;
                  stopScanBLE();
                  invalidateOptionsMenu();
              }
          }, SCAN_PERIOD);

          mScanning = true;
          startScanBLE();
      } else {
          mScanning = false;
          stopScanBLE();
      }
      invalidateOptionsMenu();
  }

  // BLE 스캔시작
  private void startScanBLE(){
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
      {
          mBluetoothAdapter.getBluetoothLeScanner().startScan(mScanCallback);
      }
      else
      {
          mBluetoothAdapter.startLeScan(mLeScanCallback);
      }
  }

  // BLE 스캔중지
  private void stopScanBLE(){
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
      {
          mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
      }
      else
      {
          mBluetoothAdapter.stopLeScan(mLeScanCallback);
      }
  }

BLE 기기가 스캔되면 콜백함수가 호출된다.

여기서 찾은 기기를 리스트 객체에 추가하고, 화면을 갱신한다.

  // BLE 기기가 스캔되면 호출. 롤리팝 이하 버전
  private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {

      @Override
      public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
          runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  mLeDeviceListAdapter.addDevice(device);
                  mLeDeviceListAdapter.notifyDataSetChanged();
              }
          });
      }
  };

  // BLE 기기가 스캔되면 호출. 롤리팝 이상 버전
  private ScanCallback mScanCallback = new ScanCallback() {
      @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
      @Override
      public void onScanResult(int callbackType, ScanResult result) {
          super.onScanResult(callbackType, result);

          BluetoothDevice device = result.getDevice();
          mLeDeviceListAdapter.addDevice(device);
          mLeDeviceListAdapter.notifyDataSetChanged();
      }
  };

3. BLE CONNECT

검색된 BLE 리스트에서 특정 기기를 선택하면

mBluetoothLeService 의 connect 메소드를

호출하여 기기에 연결을 시도한다.

  private BluetoothGatt mBluetoothGatt;

  private BluetoothGattCharacteristic mReadCharacteristic = null;
  private BluetoothGattCharacteristic mWriteCharateristic = null;

  private final String SERVICE = "0000fff0-0000-1000-8000-00805f9b34fb";
  private final String WRITE_UUID = "0000fff1-0000-1000-8000-00805f9b34fb";
  private final String READ_UUID = "0000fff2-0000-1000-8000-00805f9b34fb";

  private boolean findGattServices() {
    List<BluetoothGattService> gattServices = mBluetoothGatt.getServices();
    if (gattServices == null) return false;

    mReadCharacteristic = null;
    mWriteCharateristic = null;

    for (BluetoothGattService gattService : gattServices){
      HashMap<String, String> currentServiceData = new HashMap<String, String>();

      if(gattService.getUuid().toString().equals(SERVICE)){
        List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();

        // Loops through available Characteristics.
        for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics){
          if(gattCharacteristic.getUuid().toString().equals(READ_UUID)){
            final int charaProp = gattCharacteristic.getProperties();

            if((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0){
              try{
                mReadCharacteristic = gattCharacteristic;
                List<BluetoothGattDescriptor> list = mReadCharacteristic.getDescriptors();
                Log.d(TAG, "read characteristic found : " + charaProp);

                mBluetoothGatt.setCharacteristicNotification(gattCharacteristic, true);
                //리시버 설정
                BluetoothGattDescriptor descriptor = mReadCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                mBluetoothGatt.writeDescriptor(descriptor);
              }
              catch (Exception e){
                e.printStackTrace();
                return false;
              }
            }
            else{
              Log.d(TAG, "read characteristic prop is invalid : " + charaProp);
            }
          }
          else if(gattCharacteristic.getUuid().toString().equalsIgnoreCase(WRITE_UUID)){
            final int charaProp = gattCharacteristic.getProperties();

            if((charaProp | BluetoothGattCharacteristic.PROPERTY_WRITE) > 0){
              Log.d(TAG, "write characteristic found : " + charaProp);
              mWriteCharateristic = gattCharacteristic;
            }
            else{
              Log.d(TAG, "write characteristic prop is invalid : " + charaProp);
            }
          }
        } 
      }
    }
    return true;
  }

BLE 기기는 특정 기능을 보유하고 있는 특성(Characteristic)과

그 특성들의 집합인 서비스(Service)로 구성되어 있다.

기기의 데이터를 읽고 쓰는 작업을 할 때도 이 특성들을 사용한다.

기기와 연결을 성공하면 해당 기기의 서비스와 특성들을 찾아

사용할수 있도록 등록해야 한다.

특성의 등록이 완료되면 기기 연결의 성공여부를

onConnectionStateChange() 메서드에서 받을 수 있다.

4. BLE 데이터 통신

연결이 성공하면 기기와 데이터를 주고받을 수 있다.

기기로 전송할 Command를 가공하여 실행하면 된다.

  public void writeData(byte[] data){
    try{
        sendData = null;
        boolean result = false;
        if(mBluetoothGatt.connect())
        {
            if(mWriteCharateristic == null){
                Log.i(TAG, "Write gatt characteristic is null");
            } else if(mReadCharacteristic == null){
                Log.i(TAG, "Read gatt characteristic is null");
            } else {
                int dataLen = data.length;

                String sendDataG = "";
                for(int i=0; i<data.length; i++){
                    sendDataG += String.format("%02x", data[i]);
                }
                System.out.println("BLE Command 데이터 : " + sendDataG);
                sendData = data;

                Handler hd = new Handler();
                hd.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mWriteCharateristic.setValue(sendData);

                        if(mWriteCharateristic == null){
                            Log.d("Ble UUID가 없습니다.", "");
                            mBluetoothGatt.disconnect();
                        }else {
                            mBluetoothGatt.writeCharacteristic(mWriteCharateristic);
                        }
                    }
                },5);
            }
        }
        else
        {
            Log.i(TAG, "Bluetooth gatt is not connected");
        }
    }
    catch (Exception e)
    {
        Log.e("", e.toString());
    }
  }

기기에 Command를 전송하였을때 Response 데이터가 있다면

응답은 onCharacteristicChanged() 메서드로 콜백된다.

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
  .
  .
  .
  .

  @Override
  public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
    try
    {
        byte[] readByte = characteristic.getValue();
        if(mBlelistner != null)
          mBlelistner.recvData(readByte); // 기기 응답을 받는 리스너
    }
    catch(Exception e)
    {
        Log.d(TAG, e.toString());
    }
  }
}

getValue() 값이 기기의 Response 데이터이다.

응답을 처리할 리스너를 등록하여 데이터를 처리한다.

JAVASCRIPT, REACT, NODE, GOOGLE, MAP