반응형

1. 주변 BLE Device Scan

2022.07.07 - [Flutter/기능 구현] - [Flutter / BLE] 기능 구현 - SCAN 글을 참고하여

주변 BLE Device Scan을 수행한다.

해당 링크에 첨부된 main.dart를 참고하면 검색된 BLE Device Name을 title로 가지는

ListTile의 trailing을 터치했을 때 수행되는 onTab 속성이 주석 처리 되어있다.

Connect를 수행하기 위해서는 해당 주석을 해제하여 수행하도록 한다.

child: ListTile(
  dense: true,
  enabled: !(!_connected && _scanning),
  trailing: GestureDetector(
    behavior: HitTestBehavior.translucent,
    onTap: () {
      //(!_connected && _scanning) || (!_scanning && _connected)? (){}: onConnectDevice(index);
    },
    child: Container(
      width: 48,
      height: 48,
      padding: const EdgeInsets.symmetric(vertical: 4.0),
      alignment: Alignment.center,
      child: const Icon(Icons.add_link),
    ),
  ),
  subtitle: Text(_foundBleUARTDevices[index].id),
  title: Text("${_foundBleUARTDevices[index].name}",style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
),

2. onConnectDevice 함수 정의

검색된 주변 BLE 장치 이름을 가지는 ListTile을 터치 했을 때 호출되는 onConnectDevice 함수를 정의 한다.

onConnectDevice 에서는 Connect 관련 처리를 수행하고, 송수신되는 데이터의 처리를 담당하는 datadisplay.dart를 

호출하여 화면을 전환 후 관련처리를 하도록한다.

void onConnectDevice(index) {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) => DataDisplay(
        foundDeviceId: _foundBleUARTDevices[index].id,
        foundDeviceName: _foundBleUARTDevices[index].name,
        bleLib:  flutterReactiveBle,
      ),
    ),
  );
}

3. datadisplay.dart의 Connect 처리

datadisplay 클래스는 호출되자 마자 수행되는 override 함수인 initState 함수에서 Connect 처리를 수행한다.

Connect 처리에 관련된 소스는 2022.07.01 - [Flutter/기능 구현] - [Flutter / BLE] flutter_reactive_ble 라이브러리

글의 5.Establishing Connection 절을 참고하여 작성되었다.

@override
void initState() {
  super.initState();
  //안드로이드의 경우 범위 내에 있지 않은 장치에 연결을하려고 할 때
  //중단되는 문제가 있으므로 connectToDevice가 아닌 connectToAdvertisingDevice로
  //Connect 수행
  _currentConnectionStream = widget.bleLib.connectToAdvertisingDevice(
    id:widget.foundDeviceId,  //장치 SCAN을 통해 검색된 장치ID(안드로이드의 경우 MAC주소)로 연결
    prescanDuration: const Duration(seconds: 10),  //연결을 시도하기 전에 BLE스택이 장치를 검색하는 시간
    withServices: [globalVal.UART_UUID, globalVal.UART_RX, globalVal.UART_TX], //사용할 서비스 UUID
  );
  //BLE 상태 감시
  connection = _currentConnectionStream.listen((event) {
    if(event.failure != null){
      print("Connect Fail${event}\n");
    }
    var id = event.deviceId.toString();
    switch (event.connectionState) {
      case DeviceConnectionState.connecting:
        {
          print("Connecting to ${id} \n");
          break;
        }
      case DeviceConnectionState.connected:
        {
          globalVal.connected = true;
          print("Connected to ${id} \n");
          //수신 특성 등록
          _txCharacteristic = QualifiedCharacteristic(
              serviceId: globalVal.UART_UUID,
              characteristicId: globalVal.UART_TX,
              deviceId: event.deviceId);
          //수신
          _receivedDataStream =
              widget.bleLib.subscribeToCharacteristic(_txCharacteristic);
          _receivedDataStream.listen((data) {
            onNewReceivedData(data);
          });
          _rxCharacteristic = QualifiedCharacteristic(
              serviceId: globalVal.UART_UUID,
              characteristicId: globalVal.UART_RX,
              deviceId: event.deviceId);
          _timer = Timer.periodic(Duration(seconds: 2), (timer) { _sendData(); });
          break;
        }
      case DeviceConnectionState.disconnecting:
        {
          print("Disconnecting from $id\n");
          break;
        }
      case DeviceConnectionState.disconnected:
        {
          _timer.cancel();
          globalVal.connected = false;
          print("Disconnected from $id\n");
          break;
        }
    }
    setState(() {});
  },);
}

datadisplay.dart
0.00MB
global.dart
0.00MB
main.dart
0.01MB

반응형
반응형

1. Android 권한 설정

2022.07.04 - [Flutter] - [BLE]Android 권한 설정 참조

 

2. flutter_reactive_ble 라이브러리 설치

2022.07.01 - [Flutter] - [BLE]flutter_reactive_ble 라이브러리 의 1.SDK Install 참조

 

3. 검색하고자하는 서비스 UUID 정의

//Service UUID
Uuid _UART_UUID = Uuid.parse("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");

모든 BLE 장치를 검색하는 경우 정의하지 않아도 된다.

 

3. flutter_reactive_ble 라이브러리 초기화 및 변수 선언

final flutterReactiveBle = FlutterReactiveBle(); //flutter_reactive_ble 라이브러리 초기화
List<DiscoveredDevice> _foundBleUARTDevices = []; //검색된 BLE_UART장치 List를 저장
bool _scanning = false; //SCAN 작업 진행 상태 저장
bool _connected = false;  //연결 상태 저장

4. Scan시작 함수 정의

Future<void> _startScan() async {
    if (Platform.isAndroid) {
      if(await checkIfPermissionGranted()) {
        if(_scanning==true) {
          _stopScan();
        }
        _foundBleUARTDevices = [];
        _scanning = true;
        setState(() {});
        _scanStream = flutterReactiveBle.scanForDevices(
          withServices: [_UART_UUID]).listen((device) {
          //withServices: []).listen((device) {
            if (_foundBleUARTDevices.every((element) => element.id != device.id)) {
              if(device.name.contains('ACRO')) {
                _foundBleUARTDevices.add(device);
                print("${device.name}\n");
                setState(() {});
              }
            }
          }, onError: (Object error) {
            print("${_logTexts}ERROR while scanning:$error \n");
          }
        );
      } else {
        await showNoPermissionDialog();
        openAppSettings();
      }
    }
    return Future<void>.value();
}

[전체 소스]

main.dart
0.01MB

 

 

 

 

반응형
반응형

1. pubspec.yaml 파일 수정

pubspec.yaml 파일을 열고 아래와 같이 permission_handler 값을 지정한 후

터미널에서 flutter pub get을 실행해 준다.

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  flutter_reactive_ble: ^5.0.2
  permission_handler: ^10.0.0
PS C:\Users\eangg\Desktop\Project\flutterproject\bleTest> flutter pub get
Running "flutter pub get" in bleTest...                          2,424ms
PS C:\Users\eangg\Desktop\Project\flutterproject\bleTest>

2.AndroidManifest.xml 파일 수정

android/app/src/main/AndroidManifest.xml 파일을 열고 아래와 같이

uses-permission값을 지정한다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.bletest">
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
   <application

위의 내용은 Android 12 아래 버전에 해당하며, Android 12부터는

uses-permission의 내용을 아래와 같이 변경된다.

Android 12 부터는 위치 관련 기능 없이 BLE만 구현한다면

ACCESS_FINE_LOCATION을 추가하지 않아도 되지만

하위 버전 호환을 위해 추가한다.

<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Android 12 이상에서는 BLE에서 위치 정보를 사용하지 않을 경우
ACCESS_FINE_LOCATION을 추가하지 않아도 되지만 하위 호환성을 위해 추가한다.-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

4. build.gradle 수정

android/app/src/build.gradle에서 아래와 같이

compileSdkVersion을 33으로 변경한다.

android {
    //compileSdkVersion flutter.compileSdkVersion
    compileSdkVersion 33

minSdkVersion을 21로 변경한다.

defaultConfig {
    // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
    applicationId "com.example.ble_example"
    // You can update the following values to match your application needs.
    // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
    //minSdkVersion flutter.minSdkVersion
    minSdkVersion 21

3. dart파일 내용 추가

permission 관련 처리를 수행하는 dart 파일을 열고 아래와 같이

permission_handler.dart를 import한다.

import 'package:permission_handler/permission_handler.dart';

Future 함수를 정의하여 사용자에게 권한을 요청하도록 하고,

권한을 부여 받으면 BLE 관련 처리를 시작할 수 있도록 파일을 작성한다. 

Future<bool> checkIfPermissionGranted() async {
    Map<Permission, PermissionStatus> statuses = await[
      // 위치 권한 허용 (Android 12 이상에서는 BLE에서 위치 정보를 사용하지 않을 경우
      // location을 추가하지 않고 bluetoothScan만 있으면 되지만 하위 호환성을 위해
      // 추가한다.
      Permission.location,
      Permission.bluetoothScan, // Android 12이상부터 추가된 근처 기기 접근 권한 허용
    ].request();

    bool permitted = true;

    statuses.forEach((permission, permissionStatus) {
      if (!permissionStatus.isGranted)
        permitted = false;
    });
    return permitted;
  }
void _startScan() async {
    if (Platform.isAndroid) {
      if(await checkIfPermissionGranted()) {
          _foundBleUARTDevices = [];

 

반응형
반응형

웹서핑으로 알아본 결과 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