[iOS] CoreBluetooth를 이용한 주변기기 탐색과 연결

유인호·2024년 6월 18일
0

iOS

목록 보기
54/73
post-custom-banner

0. 서론

Bluetooth에 대한 무지에서 시작된 공부, 사실 Bluetooth의 경우 특별히 모듈이나 장치를 만들지 않으면 경험해보긴 쉽진 않기에, 근처의 주변기기를 탐색하고 연결정도 해보는 BluetoothManager를 구현해볼 예정이다.

사실 아두이노를 갖고 있고 어케 만드는지 알고 있어 아두이노로 만들면 되긴하는데,,,, 귀찮음 이슈로...

1. CoreBluetooth에 대한 설명은

[iOS] BLE with CoreBluetooth 여기서 확인

2. BluetoothManger 설계도

  1. SwiftUI에서 사용하기 쉽도록 ObservableObject를 상속받아 @Published로 현재 Connect가능한 기기들을 List로 보여준다.
  2. View에서는 Toggle로 On, Off만 간섭하고, List안에 있는 Button으로 Connect할 수 있게 한다.
  3. Delegate들을 Extension으로 나눠 책임별로 모아볼 수 있게 한다.

3. 코드

// BluetoothManager.swift
import Foundation
import CoreBluetooth

final class BluetoothManager: NSObject, ObservableObject {
    override init() {
        super.init()
        self.centralManager = CBCentralManager(delegate: self, queue: nil)
    }
    
    @Published var discoveredPeripherals: [CBPeripheral] = []
    @Published var isScanning = false {
        didSet {
            switch isScanning {
            case true:
                startScanning()
            case false:
                stopScanning()
            }
        }
    }
    
    private var centralManager: CBCentralManager!
    private var connectedPeripheral: CBPeripheral?
}

extension BluetoothManager: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .unknown:
            print("unknown")
        case .resetting:
            print("resetting")
        case .unsupported:
            print("unsupported")
        case .unauthorized:
            print("unauthorized")
        case .poweredOff:
            print("poweredOff")
        case .poweredOn:
            print("poweredOn")
        @unknown default:
            print("unknown default")
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if !discoveredPeripherals.contains(where: { $0.identifier == peripheral.identifier }) {
            discoveredPeripherals.append(peripheral)
        }
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        connectedPeripheral = peripheral
        peripheral.delegate = self
        peripheral.discoverServices(nil)
        print(peripheral)
    }

    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("Failed to connect to \(peripheral.name ?? "a peripheral"): \(error?.localizedDescription ?? "Unknown error")")
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        if connectedPeripheral == peripheral {
            connectedPeripheral = nil
        }
    }
    
    private func startScanning() {
        centralManager.scanForPeripherals(withServices: nil, options: nil)
    }
    
    private func stopScanning() {
        centralManager.stopScan()
    }
    
    func connectToPeripheral(_ peripheral: CBPeripheral) {
        centralManager.connect(peripheral, options: nil)
    }
}

extension BluetoothManager: CBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        if let services = peripheral.services {
            for service in services {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if let characteristics = service.characteristics {
            for characteristic in characteristics {
                peripheral.readValue(for: characteristic)
                peripheral.setNotifyValue(true, for: characteristic)
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if let value = characteristic.value {
            print(value.base64EncodedData())
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print("Error writing value for characteristic \(characteristic.uuid): \(error.localizedDescription)")
        } else {
            print("Successfully wrote value for characteristic \(characteristic.uuid)")
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print("Error updating notification state for characteristic \(characteristic.uuid): \(error.localizedDescription)")
        } else {
            print("Notification state updated for characteristic \(characteristic.uuid)")
        }
    }
}

// ContentView.swift
import SwiftUI

struct ContentView: View {
    @StateObject private var bluetoothManger = BluetoothManager()
    var body: some View {
        VStack {
            Toggle(isOn: $bluetoothManger.isScanning) {
                Text("스위치")
            }
            .padding()
            
            List(bluetoothManger.discoveredPeripherals, id: \.identifier) { item in
                Button {
                    bluetoothManger.connectToPeripheral(item)
                } label: {
                    Text(item.name ?? item.identifier.uuidString)
                }
            }
        }
        
    }
}

4. Result

Toggle을 통해 스위치를 열면 주변의 기기들을 찾아주고, 항목을 탭 하게 되면 연결된다.

<CBPeripheral: 0x302339040, identifier = 3E853202-F2D7-7117-ADFB-FEA5E8158B54, name = A_Mcflurry의 노트북, mtu = 23, state = connected>
QXBwbGUgSW5jLg==
Error updating notification state for characteristic Manufacturer Name String: The request is not supported.
TWFjQm9va1BybzE4LDM=
Error updating notification state for characteristic Model Number String: The request is not supported.
Notification state updated for characteristic Continuity
LQAgBAAEAFrxsfkAAAAAsAL//+oDAAACBAUoAQMDBgEBCAoVHyN7AAAAAAQCAAA=
LQAgBAAEAFrxsfkAAAAAsAL//7aVAAACBAUoAQMDBgEBCAoVH1bpAAAAAAQCAAA=

Peripheral의 정보와 데이터를 수신받게 되는데, 저 데이터가 무슨 데이터인질 아직 잘 모르겠다.

5. 결론

간단한 토이프로젝트로 CoreBluetooth를 이용한 탐색과 연결을 해보았다. Bluetooth 연결에 대한 개념을 학습할 수 있었고, 간단한 플젝으로 연결도 성공, 데이터도 수신받아보았다.

profile
🍎Apple Developer Academy @ POSTECH 2nd, 🌱SeSAC iOS 4th
post-custom-banner

2개의 댓글

comment-user-thumbnail
2024년 6월 19일

삐뽀 잔다 😴

1개의 답글