반응형

웹서핑으로 알아본 결과 flutter에서 사용할 수 있는 BLE 라이브러리는 두세가지 있으나,

그 중 flutter_reactive_ble가 지속적인 업데이트나 Null Safety로 구현되어 있다고

되어 있는 것으로 파악되어  flutter_reactive_ble를 사용하여 구현하도록 한다.

 

우선 flutter_reactive_ble에 Read Me 내용을 정리한다.

 

https://pub.dev/packages/flutter_reactive_ble

 

flutter_reactive_ble | Flutter Package

Reactive Bluetooth Low Energy (BLE) plugin that can communicate with multiple devices

pub.dev

 

1. SDK Install 

SDK 설치를 위해 아래와 같이 터미널에서 flutter명령을 수행한다.

$ flutter pub add flutter_reactive_ble

설치가 완료되면 pubspec.yaml에 다음과 같이 내용이 추가된다.

dependencies:
  flutter_reactive_ble: ^5.0.2

이제 기능을 구현하는 Dart 파일에서 아래와 같이 패키지를 Import하여 개발을 진행한다.

import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';

2. Initialization

다음과 같이 라이브러리 초기화를 수행한다.

final flutterReactiveBle = FlutterReactiveBle();

3. Device Discovery

다음과 같이 BLE 장치 검색을 수행한다.

flutterReactiveBle.scanForDevices(
	withServices: [serviceId],scanMode: ScanMode.lowLatency).listen((device) {
      //code for handling results
    }, onError: () {
      //code for handling error
    })

withServices는 검색할 Service UUID를 지정하며, 지정하지 않으면 모든

Advertising Device를 검색한다.

scanMode는 안드로이드에서만 사용되는 것으로 Android Reference Page의 ScanSettings

설명된 규칙을 따르며, 지정하지 않으면 balanced scan mode가 사용된다.

 

4. Observe Host Device BLE Status

statusStream은 Host Device(앱이 실행되는 장치)의 BLE 상태에 대한 업데이트를

검색하는데 사용된다.

이 스트림은 BLE가 켜져 있는지, 장치에서 필요한 권한이 부여되었는지 확인하는데

사용할 수 있다.

_ble.statusStream.listen((status) {
  //code for handling status update
});

_ble.status는 호스트 장치의 현재 상태를 가져오는데 사용한다.

다양한 상태의 의미에 대한 자세한 내용은 BleStatus를 참조한다.

 

5. Establishing Connection

검색된 장치와 상호 작용하려면 먼저 연결을 설정해야 한다.

flutterReactiveBle.connectToDevice(
      id: foundDeviceId,
      servicesWithCharacteristicsToDiscover: {serviceId: [char1, char2]},
      connectionTimeout: const Duration(seconds: 2),
    ).listen((connectionState) {
      // Handle connection state updates
    }, onError: (Object error) {
      // Handle a possible error
    });

id는 장치 검색을 통해 검색된 장치ID를 사용한다. iOS에서 장치ID는 UUID이고,

Android에서는 MAC주소(Android버전에 따라 바뀔 수 있음)이다.

검색하려는 Service ID와 Chracteristic ID가 포함된 Map을 제공하면 iOS에서

연결 속도가 빨라질 수 있다(그렇지 않으면 모든 Service 및 Chracteristic이 검색됨). 

connectionTimeout은 지정된 시간 내에 연결을 설정할 수 없는 경우 클라이언트에서

오류를 제공할 시간을 지정할 수 있다.

Android BLE 스택은 범위 내에 있지 않은 장치에 연결하려고 할 때 중단되는

문제가 있다. 이 문제를 해결하기 위해 connectToAdvertisingDevice를 사용하여

먼저 장치를 검색하고 장치가 발견된 경우에만 연결한다.

flutterReactiveBle.connectToAdvertisingDevice(
    id: foundDeviceId,
    withServices: [serviceUuid],
    prescanDuration: const Duration(seconds: 5),
    servicesWithCharacteristicsToDiscover: {serviceId: [char1, char2]},
    connectionTimeout: const Duration(seconds:  2),
  ).listen((connectionState) {
    // Handle connection state updates
  }, onError: (dynamic error) {
    // Handle a possible error
  });

위에 설명된 매개변수 외에도 이 기능에는 2개의 매개변수 withServices와

prescanDuration이 있다.

prescanDuration은 연결을 시도하기 전에 BLE 스택이 장치를 검색하는

시간이다(장치가 있는 경우).

 

6. Read Characteristics

final characteristic = QualifiedCharacteristic(
    serviceId: serviceUuid, 
    characteristicId: characteristicUuid, 
    deviceId: foundDeviceId);
final response = await flutterReactiveBle.readCharacteristic(characteristic);

7. Write with Response

Characteristic에 값을 쓰고 Response를 기다린다. "Write with Response"에서

"Response"는 "수신 확인"을 의미한다.

Write는 수신확인(성공)되거나 실패(예외가 발생)할 수 있으므로,

Return Type은 void이고 print할 항목이 없다.

(catch문을 통하여 print("Write successful") 또는 print("Write failed: $e")을

출력할 수 있다.)

BLE는 기본적으로 HTTP에서와 같은 Request-Response 메커니즘을 제공하지 않는다.

Request-Response 호출을 수행해야하는 경우 기본 BLE 기능 위에

사용자 지정 메커니즘을 구현해야 한다.

일반적인 접근 방식은 Write가 가능하고 Notifications 또는 Indications 를 전달하는

특성인 "Control Point"를 구현하여, Request가 기록되고 응답이 Notification

또는 Indication으로 다시 전달되도록 하는 것이다.

final characteristic = QualifiedCharacteristic(
    serviceId: serviceUuid, 
    characteristicId: characteristicUuid, 
    deviceId: foundDeviceId); 
await flutterReactiveBle.writeCharacteristicWithResponse(characteristic, value: [0x00]);

8. Write without Response

짧은 시간 내에 여러 개의 연속 쓰기 작업을 실행하려는 경우(예: 장치에 펌웨어 업로드)

또는 장치가 응답을 제공하지 않는 경우 이 작업을 사용한다.

이것은 성능 면에서 값을 쓰는 가장 빠른 방법이지만 BLE 장치가 연속적으로

많은 쓰기를 처리할 수 없을 가능성이 있으므로 Write with Response를

한번씩 수행하도록 한다.

final characteristic = QualifiedCharacteristic(
    serviceId: serviceUuid, 
    characteristicId: characteristicUuid, 
    deviceId: foundDeviceId);
flutterReactiveBle.writeCharacteristicWithoutResponse(characteristic, value: [0x00]);

9. Subscribe to Chracteristic

주기적으로 Chracteristic을 읽는 대신 값이 변경되는 경우(특정 서비스에서 지원하는 경우)

Notification을 읽을 수 있다.

final characteristic = QualifiedCharacteristic(
    serviceId: serviceUuid, 
    characteristicId: characteristicUuid, 
    deviceId: foundDeviceId);
flutterReactiveBle.subscribeToCharacteristic(characteristic).listen((data) {
      // code to handle incoming data
    }, onError: (dynamic error) {
      // code to handle errors
    });

10. Negotiate MTU size

MTU 크기를 늘리거나 줄여 더 높은 처리량을 구현할 수 있다.

이작업은 실제 Negotiated MTU 크기를 반환하지만 요청한 크기가

성공적으로 Negotiate 된다는 보장은 없다.

iOS에는 협상할 수 없는 기본 MTU 크기가 있지만 이 작업을 사용하여

현재 MTU를 가져올 수 있다.

final mtu = await flutterReactiveBle.requestMtu(
    deviceId: foundDeviceId, mtu: 250);

11. Android Specific Operations

다음 작업은 Andaroid에서만 적용되며 iOS에서는 지원되지 않는다.

iOS에서 이러한 작업을 사용하면 라이브러리에서

UnSupportedOperationException이 발생한다.

(1) Request Connection Priority

Android에서는 BLE 장치에 연결 우선 순위 업데이트를 보낼 수 있다.

priority 매개변수는 BluetoothGatt Android spec.과 동일한 열거형 사양을 가진다.

사용하면 배터리 사용량이 증가할 수 있지만 GATT 작업 속도가 빨라질 수 있다.

여러 장치의 우선 순위를 설정할 때 모든 장치에 highPerformance를 설정하면

우선 순위를 높이는 효과가 낮아지므로 주의해야한다.

await flutterReactiveBle.requestConnectionPriority(
    deviceId: foundDeviceId, 
    priority:  ConnectionPriority.highPerformance);

(2) Clear GATT cache

Android OS는 발견된 서비스의 기기별 테이블을 캐시에 유지한다.

때때로 펌웨어 업데이트 후 새로운 서비스가 생성되었지만

캐시가 업데이트 되지 않는 경우가 발생한다.

캐시를 무효화하려면 cleargattCatche 작업을 사용할 수 있다.

이 작업은 숨겨진 BLE 작업이며, graylist에 있으므로 매우 주의해서 사용해야 한다.

await flutterReactiveBle.clearGattCache(foundDeviceId);

12.FAQ

(1) How to Handle the BLE Undeliverable Exception

Android에서는 Polidea의 RxAndroidBle라이브러리를 사용한다.

RxJave 2로 마이크레이션한 후 일부 오류가 해당 lisner로 전달되지 않아

BLE Undeliverable Exception 이 발생한다.

근본 원인은 Android OS의 스레딩에 있다.

해결 방법으로 RxJave에는 global errorhandler를 설정할 수 있는 hook가 있다.

자세한 내용은 RxJava 문서를 참조한다.

Flutter 앱의 기본 해결 방법 구현은 Java/Kotlin 부분(예 : mainactivity)에 있어야 한다.

예제(Java)는 Polidea RxAndroidBle Sample을 참조한다.

BleException은 Polidea RxAndroidBle에서 발생하므로 애플리케이션이 

다음 종속성을 선언하는지 확인해야 한다.

implementation "com.polidea.rxandroidble2:rxandroidble:1.11.1"
RxJavaPlugins.setErrorHandler { throwable ->
  if (throwable is UndeliverableException && throwable.cause is BleException) {
    return@setErrorHandler // ignore BleExceptions since we do not have subscriber
  }
  else {
    throw throwable
  }
}

(2) Which Permissions are needed?

- Android

Android의 경우 라이브러리는 다음 권한을 사용한다.

  • ACCESS_FINE_LOCATION : 오래된 Nexus 기기가
    안정적인 스캔 결과를 제공하기 위해 위치 서비스가 필요하기 때문에
    이 권한이 필요하다.
  • BLUETOOTH : 앱이 페어링된 블루투스 장치에 연결할 수 있도록 한다.
  • BLUETOOTH_ADMIN : 앱이 블루투스 기기를 검색하고 페어링할 수 있도록 한다.

이러한 권한은 이미 라이브러리의 manifest에 추가되어 있으므로

앱의 manifest에 자동으로 병합되어야 한다.

menifest에 권한을 추가할 필요는 없다.

- iOS

iOS의 경우 앱의 Info.plist 파일에 다음 항목을 추가해야 한다.

그렇지 않으면 Core BLuetooth에 액세스할 수 없다.

이를 구현하는 방법에 대한 예제를 참조한다.

자세한 내용은 iOS 블루투스 권한에 대한 블로그 게시물을 참조한다.

 

iOS13 and higher

  • NSBluetoothAlwaysUsageDescription

iOS12 and lower

  • NSBluetoothPeripheralUsageDescription

(3) How to adjust ProGuard (Android)

ProGuard를 사용하는 경우 proguard-rules.pro 파일에 다음 스피넷을 추가한다.

-keep class com.signify.hue.** { *; }

(4) Why doesn't the BLE stack directly connect to my peripheral

BLE 작업을 실행하기 전에 장치의 BLE 스택에서 모든 것이 올바르게 설정되었는지

확인한 다음 실행 준비가 완료되었음을 보고한다.

일부 장치의 경우 이 작업이 다른 장치보다 시간이 조금 더 걸린다.

앱을 시작할 때 BLE작업을 실행하기 전에 BLE 스택이

제대로 초기화되었는지 확인하도록 한다.

이를 수행하는 가장 안전한 방법은 statusStream으로부터

BleStatus.ready를 기다리는 것이다.

 

반응형

'Flutter > 기능 구현' 카테고리의 다른 글

[Flutter / BLE] 기능 구현 - CONNECT  (0) 2022.07.21
[Flutter / BLE] 기능 구현 - SCAN  (0) 2022.07.07
[Flutter / BLE] Android 권한 설정  (0) 2022.07.04

+ Recent posts