League2eb

1.匯入CoreBluetooth

1
import CoreBluetooth

2.建立結構與宣告需要物件與參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct DiscoveredItem {
/*設備*/
var peripheral: CBPeripheral!
/*訊號強弱*/
var lastRSSI =
/*最後一次發現時間*/
var lastSeenDate: Date!
/*直接初始*/
init(newPeripheral:CBPeripheral, RSSI:Int) {
peripheral = newPeripheral
lastRSSI = RSSI
lastSeenDate = Date()
}
}

3.於class中加入代理,並且宣告cantralManager(這邊使用TableView來顯示資料)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class ViewController:UIViewController,CBCentralManagerDelegate,UITableViewDelegate,UITableViewDataSource {
/*宣告cantralManager*/
var cantralManager:CBCentralManager?
/*宣告一個字典,這邊的Value為上面的結構型態*/
var allItems = [String:DiscoveredItem]()
/*宣告即將要搜尋的設備,這邊我們使用的是速度踏頻器、心跳帶作為範例*/
let CYCLING_SPEED_AND_CADENCE_SERVICE_UUID:CBUUID = CBUUID(string:"1816")
let HR_SERVICE_UUID:CBUUID = CBUUID(string:"180D")
/*宣告心跳帶服務中的特徵,基本有3三個。*/
//可以參考:https://goo.gl/Iyr31u

let HR_CHARACTERISTIC_UUID:CBUUID = CBUUID(string:"2A37")
/*宣告心跳帶的特徵並把初始數值指定為nil*/
var HR_Characteristic:CBCharacteristic? = nil
}

4.於ViewDidLoad把cantralManage加入到主執行緒

1
2
3
4
override func viewDidLoad() {
super.viewDidLoad()
centralManager = CBCentralManager(delegate: self, queue: nil)
}

5.偵測Centrel的狀態
變數centralState拿來儲存狀態

1
2
3
4
5
6
7
8
func centralManagerDidUpdateState (_ central: CBCentralManager) {
guard central.state == .powerdOn else {
let centralState = central.state.rawValue
return
}
let centralState = central.state.rawValue
print("目前狀態為\(centralState)")
}

6.設定Scan,並啟動 (看需求決定,可以在ViewDidLoad或者一個按鈕中)
如果你還看不懂下面在幹嘛,你就複製然後寫成一個方法在ViewDidLoad呼叫

1
2
3
4
5
6
7
/*宣告將被搜尋的Service的UUID並加入一個陣列中*/
let services = [CYCLING_SPEED_AND_CADENCE_SERVICE_UUID,HR_SERVICE_UUID]/*這兩個已經宣告並指定在上面了...*/
/*AllowDuplicatesKey的意思是若有一樣的設備就區分開回傳,true是為了避免重複*/
let options = [CBCentralManagerScanOptionAllowDuplicatesKey:true]
/*若withServices的參數給予nil則表示所有設備都掃描,但會比較耗電、慢。建議要有指定*/
centralManager?.scanForPeripherals(withServices: services, options: options)
/*當開始搜尋設備的時候,不會有任何動作。但如果有設備被搜尋到了就會觸發didDiscoverperipheral這個代理(第9步)*/

7.當有設備被搜尋到的時候會觸發的方法,並有peripheral(設備)、advertisementData(設備參數)、訊號強弱等三個參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
/*把搜尋到的設備內容丟進existItem常數中*/
let existItem = allItems[peripheral.identifier.uuidString]
/*繼續搜尋並且把新的設備加入到newItem常數中,這邊的newPeripheral、RSSI為DiscoveredItem初始化會呼叫的物件*/
let newItem = DiscoveredItem(newPeripheral: peripheral, RSSI: Int(RSSI))
/*當繼續搜尋後有新設備,就把上面這個newItem加到allItems這個全域變數中*/
allItems[peripheral.identifier.uuidString] = newItem
/*宣告一個日期等等會使用*/
let now = Date()
/*宣告一個變數用來儲存上一次刷新時間用*/
var lastReloadDate: Date?
/*判斷,假設沒有新的設備、上次刷新時間不存在、距離上次刷新已經超過2秒,就把上次刷新時間等於現在時間並且做一次刷新列表*/
if existItem == nil || lastReloadDate == nil || now.timeIntervalSince(lastReloadDate!) > 2.0 {
lastReloadDate = now
tableView.reloadData()
}
}

8.創建與設備連結的方法

1
2
3
4
5
6
7
8
9
10
11
/*這個方法有帶了一個參數型態為IndexPath*/
func startConnect(indexPatch:IndexPath) {
/*把搜尋到的設備ID存到型態為allKeys的陣列中*/
let allKeys = Array(allItems.keys)
/*把選擇到的設備編號丟到常數targerKey中*/
let targerKey = allKeys[indexPatch.row]
/*再把這個targerKey中的參數丟到targetItem常數中*/
let targetItem = allItems[targerKey]
/*最後就是與設備連結*/
centralManager?.connect(targetItem!.peripheral, options: nil)
}

9.觸發與設備連結,這邊我們使用的是TableView的accessoryButtonTap,當然你也可以寫在didSelectRowAtIndexPath中。

1
2
3
4
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
/*呼叫*/
startConnect(indexPatch: indexPath)
}

10.第9步按下accessoryButton後便會觸發didConnect peripheral這個代理

1
2
3
4
5
6
7
8
9
10
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
/*把這個設備的名字給存起來,加以辨識*/
let name = peripheral.name ?? "UnKnow"
/*完成連結後還有事情要做,所以把自己添加給設備的代理*/
peripheral.delegate = self
/*一定要執行"discoverService"功能去尋找可用的Service*/
peripheral.discoverServices(nil)
/*此時是停止搜尋的最佳時機*/
centralManager?.stopScan()
}

10.執行peripheral.discoverServices(nil)後,便會觸發didDiscoverServices的代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
/*在連線之後嘗試把這個設備有的Services給印出來*/
print("搜尋服務已經完成,共有\(peripheral.services)")
/*如果有錯誤就執行return上的程式碼*/
guard error == nil else {
/*停止連線*/
centralManager?.cancelPeripheralConnection(peripheral)
/*繼續搜尋設備*/
return
} /*若沒有錯誤就執行以下*/
/*使用for in 迴圈把設備的Services取出,這邊的service型態為CBService*/
for service in peripheral.services ?? [] {
/*判斷uuid是否相同,這邊就以心跳帶來執行*/
if service.uuid == HR_SERVICE_UUID {
/*啟動discoverCharacteristics來觸發代理*/
peripheral.discoverCharacteristics(nil, for: service)
}
}
}

11.執行peripheral.discoverCharacteristics(nil, for: service)後,便會觸發didDiscoverCharacteristicsFor service這個代理

1
2
3
4
5
6
7
8
9
10
11
12
guard error == nil else {
return
}
for characteristic in service.characteristic ?? [] {
/*uuid相同...*/
if (characteristic.uuid == HR_CHARACTERISTIC_UUID) {
/*註冊通知,一旦有新的數據就把數據給傳入。*/
peripheral.setNotifyValue(true, for: characteristic)
/*把For迴圈宣告的變數「characteristic」丟進上面宣告型態為CBCharacteristic的HR_Characteristic*/
HR_Characteristic = characteristic
}
}

12.command+n建立一個新的Swift File 命名為「BinaryConvert」並加入以下程式碼

/這是一個Uint8的擴充/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extension UInt8
{
/*這是一個回傳布林陣列的方法,主要功能是把Uint8的值轉成Bit Array*/
func toBitArray() -> [Bool]
{
var Result:[Bool] = []
Result.append(((self) & 0x01) == 1);
Result.append(((self >> 1) & 0x01) == 1)
Result.append(((self >> 2) & 0x01) == 1)
Result.append(((self >> 3) & 0x01) == 1)
Result.append(((self >> 4) & 0x01) == 1)
Result.append(((self >> 5) & 0x01) == 1)
Result.append(((self >> 6) & 0x01) == 1)
Result.append(((self >> 7) & 0x01) == 1)
return Result;
}
}

13.第11步中,註冊了一數據通知,只要有更新就把數值傳入並且呼叫didUpdateValueFor characteristic這個Delegate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
/*把characteristic中的value拿來作為Optional Binding解包*/
if let value = characteristic.value {
/*此時,HR_Characteristic為16位元的字符。並判斷兩者是否相等...*/
if (characteristic == HR_Characteristic) {
/*以下為解碼編程,這邊的toBitArray為第13步中的方法,他的陣列中總共有8個。規範中,拿取心跳就是第0個*/
let HRFlag = value[0].toBitArray()[0]
/*這邊判斷就要看你的設備是支援UInt16還是Uint8*/
if HRFlag == true {
let hr = binaryToType([value[1],value[2]], UInt16.self)
print("UInt16...\(hr)")
} else {
let hr = binaryToType([value[1]], UInt8.self)
print("UInt8...\(hr)")
}
}
}
}

※若以上的步驟都完整的執行完畢,應該個可以拿到心跳帶回報的心跳數據


 評論