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