昨天我們完成了藍芽的基本環境設定,並建立了一個簡單的 BluetoothService 架構。
今天要繼續進化,讓我們的 App 能夠:
首先,我們定義一個 BluetoothServiceDelegate 協議,讓 Service 可以把 掃描到的裝置 和 接收到的資料 傳回 UI。
protocol BluetoothServiceDelegate: AnyObject {
func getBLEPeripherals(peripherals: [CBPeripheral]) // 回傳掃描到的裝置
func getBLEPeripheralsValue(value: String) // 回傳接收到的數據
}
透過這種設計,我們能讓 Service 與 UI 完全解耦。
UI 只要實作這兩個方法,就能即時取得藍芽掃描結果與資料更新。
接著,我們建立 BluetoothService 類別。
這裡採用 單例模式 (Singleton),確保全專案只存在一個藍芽實例,方便集中管理。
class BluetoothService: NSObject {
static let shared = BluetoothService()
weak var delegate: BluetoothServiceDelegate?
var central: CBCentralManager? // 中央管理器
var peripheral: CBPeripheralManager? // 外圍管理器
var connectedPeripheral: CBPeripheral? // 已連線裝置
var rxtxCharacteristic: CBCharacteristic? // 資料收發特徵值
private var bluePeripherals: [CBPeripheral] = []
private override init() {
super.init()
let queue = DispatchQueue.global()
central = CBCentralManager(delegate: self, queue: queue)
peripheral = CBPeripheralManager(delegate: self, queue: queue)
}
}
這邊同時初始化了 中央管理器 (Central) 與 外圍管理器 (Peripheral)。
目前我們主要使用 Central 角色,但未來若想讓手機變成 BLE 外設,也能直接擴充。
我們接著實作幾個公開方法來控制藍芽操作:
func startScan() {
central?.scanForPeripherals(withServices: nil, options: nil)
}
func stopScan() {
central?.stopScan()
}
func connectPeripheral(peripheral: CBPeripheral) {
self.connectedPeripheral = peripheral
central?.connect(peripheral, options: nil)
}
func disconnectPeripheral(peripheral: CBPeripheral) {
central?.cancelPeripheralConnection(peripheral)
}
這些方法負責控制藍芽掃描、連線與中斷:
當藍芽狀態變化或掃描到裝置時,CBCentralManagerDelegate 會觸發對應事件。
藍芽狀態監控:
func centralManagerDidUpdateState(_ central: CBCentralManager) {
switch central.state {
case .poweredOn:
print("藍牙開啟,開始掃描")
startScan()
case .poweredOff:
print("藍牙關閉")
case .unauthorized:
print("沒有藍牙權限")
default:
print("狀態: \(central.state.rawValue)")
}
}
掃描到裝置:
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi RSSI: NSNumber) {
guard !bluePeripherals.contains(where: { $0.name == peripheral.name }) else { return }
if let name = peripheral.name {
bluePeripherals.append(peripheral)
print("找到裝置: \(name)")
delegate?.getBLEPeripherals(peripherals: bluePeripherals)
}
}
成功連線:
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
print("已連線到:\(peripheral.name ?? "未知裝置")")
peripheral.delegate = self
peripheral.discoverServices(nil)
}
當成功連線後,就會進入「服務 → 特徵值 → 資料」的流程。
發現服務:
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
print("服務: \(service)")
peripheral.discoverCharacteristics(nil, for: service)
}
}
發現特徵值:
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
print("特徵值: \(characteristic)")
// 偵測特定 UUID (例如 FFE1)
if characteristic.uuid.isEqual(CBUUID(string: "FFE1")) {
rxtxCharacteristic = characteristic
peripheral.readValue(for: characteristic)
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
接收到資料:
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
guard characteristic == rxtxCharacteristic,
let data = characteristic.value,
let stringValue = String(data: data, encoding: .utf8) else { return }
print("接收到資料: \(stringValue)")
delegate?.getBLEPeripheralsValue(value: stringValue)
}
這樣就能讓 UI 端即時接收 BLE 資料更新 🎉
今天我們完成了整個藍芽連線流程的關鍵部分:
到這裡,我們已經擁有一個可「掃描 → 連線 → 收資料」的藍芽核心服務。
明天(Day28)我們將進一步整合 UI 介面,讓使用者能選擇藍芽裝置並查看即時資料更新!