Geon

iOS 앱에서 Core Bluetooth를 사용하여 BLE(Bluetooth Low Energy) 통신을 구현하는 방법은 무엇인가요 본문

iOS developer essential skills

iOS 앱에서 Core Bluetooth를 사용하여 BLE(Bluetooth Low Energy) 통신을 구현하는 방법은 무엇인가요

jgkim1008 2024. 6. 17. 23:03

Bluetooth 모드 설정

앱에서 비즈니스 로직을 처리 하려고 하여, BLE 모드로 진행하였습니다.

HID (Human Interface Device)

Bluetooth HID (Human Interface Device)는 Bluetooth 무선 장치를 키보드로 사용하도록 설계되었습니다. 이 모드는 Android, iOS, MacOS, Windows에서 장치를 키보드로 인식하기 때문에 애플리케이션이 필요하지 않습니다.

SPP

Bluetooth SPP (Serial Port Profile)는 RS-232 케이블(또는 다른 시리얼 통신 인터페이스)을 대체하기 위해 설계되었습니다. SPP는 두 장치 간의 데이터/정보를 보낼 때 훌륭합니다. Bluetooth SPP는 종종 Windows 호스트의 실제 COM 포트와 작동하는 앱과 함께 작동합니다.

BLE

이 옵션을 사용하려면 장치에 Bluetooth 4.0 이상이 있어야 합니다. BLE는 에너지 소비와 속도를 최적화하여 극히 저전력 응용 프로그램과 함께 작동하도록 설계되었습니다. BLE를 사용하면 훨씬 더 나은 사용자 경험을 얻을 수 있습니다. BLE는 호스트 장치의 Bluetooth 설정 앱을 통한 페어링이 필요하지 않습니다. 동영상에서 확인할 수 있듯이 BLE 연결은 HID보다 30배 빠릅니다.

CoreBlueTooth 사용시 BLE 모드로 셋팅하여야 합니다.

central과 Peripheral의 역할과 상호작용 과정을 설명해주세요.

  • CBCentralManager: 블루투스를 스캔, 발견, 연결을 관리하는 객체
  • CBPeripheral: 블루투스 디바이스 객체
  • CBPeripheral의 특성 서비스에 대한 데이터 수신시 2가지 방식으로 Receive 가능
    • notify
    • read

셋팅 방법(iOS)

iOS 12버전 미만은 NSBluetoothPeripheralUsageDescription 에 권한 허용필요에 대한 문구 설정

iOS 13버전 이상부터는 NSBluetoothAlwaysUsageDescription 에 권한 허용필요에 대한 문구 설정

  • CBCentralManager와 CBPeripheralManager의 주요 메서드와 델리게이트 메서드를 설명해주세요.
class BluetoothManager: NSObject, ObservableObject {
    private var centralManager: CBCentralManager!
    @Published var discoveredPeripherals: [CBPeripheral] = [] // 근처에 발견된 블루투스 기기 목록
    @Published var connectedPeripheral: CBPeripheral? // 연결된 블루투스 기기 
    @Published var receivedData: Data? // 수신된 데이터
    @Published var scannerUUID = UUID(uuidString:"BDA17FA8-0A03-28D3-55E5-5C5B106592C3") // 특정기기의 UUID(현재는 EY-015P)
    override init() {
        super.init()
        // 초기화
        self.centralManager = CBCentralManager(delegate: self, queue: nil)
    }
    // 기기 목록 선택시 peripheral를 centralManager에 등록
    func connect(peripheral: CBPeripheral) {
        centralManager.connect(peripheral, options: nil)
    }
    // 기기 목록 해제시 centralManager에 peripheral를 삭제
    func disconnect() {
        guard let connectedPeripheral = connectedPeripheral else { return }
        centralManager.cancelPeripheralConnection(connectedPeripheral)
    }
}

extension BluetoothManager: CBCentralManagerDelegate {
  //centralManager의 상태에 따라 설정할 로직
  // 현재는 블루투스가 ON 되어야만 스캔 하도록 적용
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            centralManager.scanForPeripherals(withServices: nil, options: nil)
        case .poweredOff, .unauthorized, .unknown, .resetting, .unsupported:
            print("Bluetooth is not available.")
        @unknown default:
            fatalError()
        }
    }
    // 조회되는 블루투스 기기들을 리스트에 append 시키는 로직
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if !discoveredPeripherals.contains(peripheral) {
            discoveredPeripherals.append(peripheral)
        }
    }
    // 연결된 블루투스를 변수에 할당하는 로직
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("Discovered peripheral: \(peripheral.name ?? "Unknown")")
        print("Peripheral UUID: \(peripheral.identifier)")
        connectedPeripheral = peripheral
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }
    // 연결에 실패했을경우 에러 처리
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("Failed to connect to peripheral: \(error?.localizedDescription ?? "Unknown error")")
    }
    // 해제시 로직
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        if connectedPeripheral == peripheral {
            connectedPeripheral = nil
        }
        print("Disconnected from peripheral: \(error?.localizedDescription ?? "No error")")
    }
}
extension BluetoothManager: CBPeripheralDelegate {
    // 연결된 블루투스의 특성 서비스를 조회하는 로직
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let error = error {
            print("Error discovering services: \(error.localizedDescription)")
            return
        }
        guard let services = peripheral.services else { return }
        for service in services {
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }
    // 블루투스의 특성 서비스를 조회 완료한후 해당 특성에 대한 권한 및 노티를 부여하는 로직
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let error = error {
            print("Error discovering characteristics: \(error.localizedDescription)")
            return
        }
        guard let characteristics = service.characteristics else { return }
        for characteristic in characteristics {
            peripheral.setNotifyValue(true, for: characteristic)
            peripheral.readValue(for: characteristic)
        }
    }
    // 특성 서비스로부터(read)로 데이터를 수신받을경우
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print("Error updating characteristic value: \(error.localizedDescription)")
            return
        }
        // 요기
        if let value = characteristic.value {
            print(String(data: value, encoding: .utf8))
            receivedData = value
        }
    }
    // 특성 서비스로부터(노티)로 데이터를 수신받을경우
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print("Error updating characteristic value: \(error.localizedDescription)")
            return
        }
        if let value = characteristic.value {
            print(String(data: value, encoding: .utf8))
            receivedData = value
        }
    }
}

참고 문서 링크:
https://developer.apple.com/documentation/corebluetooth