本文目錄
- 1瀑构、藍牙介紹
- 2裆针、iBeacon
- 3、iOS 藍牙
- 4寺晌、中心模式的使用
- 5世吨、外設(shè)模式的使用
- 6、后臺運行藍牙服務(wù)
- 7呻征、第三方框架
1耘婚、藍牙介紹
具體講解見 藍牙
技術(shù)信息 藍牙協(xié)議棧
2、iBeacon
具體講解見 Beacon
iBeacon 是蘋果公司 2013 年 9 月發(fā)布的移動設(shè)備用 OS(iOS7)上配備的新功能陆赋。其工作方式是沐祷,配備有低功耗藍牙(BLE)通信功能的設(shè)備使用 BLE 技術(shù)向周圍發(fā)送自己特有的 ID,接收到該 ID 的應(yīng)用軟件會根據(jù)該 ID 采取一些行動攒岛。比如赖临,在店鋪里設(shè)置 iBeacon 通信模塊的話,便可讓 iPhone 和 iPad 上運行一資訊告知服務(wù)器灾锯,或者由服務(wù)器向顧客發(fā)送折扣券及進店積分兢榨。此外,還可以在家電發(fā)生故障或停止工作時使用 iBeacon 向應(yīng)用軟件發(fā)送資訊顺饮。
蘋果 WWDC 14 之后吵聪,對 iBeacon 加大了技術(shù)支持和對其用于室內(nèi)地圖的應(yīng)用有個更明確的規(guī)劃。蘋果公司公布了 iBeacon for Developers 和 Maps for Developers 等專題頁面兼雄。
3吟逝、iOS 藍牙
3.1 常見簡稱
MFi:make for ipad ,iphone, itouch 專們?yōu)樘O果設(shè)備制作的設(shè)備,開發(fā)使用 ExternalAccessory 框架君旦。認證流程挺復(fù)雜的,而且對公司的資質(zhì)要求較高嘲碱,詳見 iOS - MFi 認證金砍。
BLE:buletouch low energy,藍牙 4.0 設(shè)備因為低耗電麦锯,所以也叫做 BLE恕稠,開發(fā)使用 CoreBluetooth 框架。
-
GATT Profile(Generic Attribute Profile):GATT 配置文件是一個通用規(guī)范扶欣,用于在 BLE 鏈路上發(fā)送和接收被稱為 “屬性”(Attribute)的數(shù)據(jù)塊鹅巍。目前所有的 BLE 應(yīng)用都基于 GATT千扶。
- 定義兩個 BLE 設(shè)備通過叫做 Service 和 Characteristic 的東西進行通信。中心設(shè)備和外設(shè)需要雙向通信的話骆捧,唯一的方式就是建立 GATT 連接澎羞。
- GATT 連接是獨占的×参基于 GATT 連接的方式的妆绞,只能是一個外設(shè)連接一個中心設(shè)備。
- 配置文件是設(shè)備如何在特定的應(yīng)用程序中工作的規(guī)格說明枫攀,一個設(shè)備可以實現(xiàn)多個配置文件括饶。
-
GAP(Generic Access Profile):用來控制設(shè)備連接和廣播,GAP 使你的設(shè)備被其他設(shè)備可見来涨,并決定了你的設(shè)備是否可以或者怎樣與合同設(shè)備進行交互图焰。
- GATT 連接,必需先經(jīng)過 GAP 協(xié)議蹦掐。
- GAP 給設(shè)備定義了若干角色技羔,主要兩個:外圍設(shè)備(Peripheral)和中心設(shè)備(Central)。
- 在 GAP 中外圍設(shè)備通過兩種方式向外廣播數(shù)據(jù):Advertising Data Payload(廣播數(shù)據(jù))和 Scan Response Data Payload(掃描回復(fù))笤闯。
Profile:并不是實際存在于 BLE 外設(shè)上的堕阔,它只是一個被 Bluetooth SIG(一個以制定藍牙規(guī)范,以推動藍牙技術(shù)為宗旨的跨國組織)或者外設(shè)設(shè)計者預(yù)先定義的 Service 的集合颗味。
Service:服務(wù)超陆,是把數(shù)據(jù)分成一個個的獨立邏輯項,它包含一個或者多個 Characteristic浦马。每個 Service 有一個 UUID 唯一標識时呀。UUID 有 16 bit 的,或者 128 bit 的晶默。16 bit 的 UUID 是官方通過認證的谨娜,需要花錢購買,128 bit 是自定義的磺陡,可以自己設(shè)置趴梢。每個外設(shè)會有很多服務(wù),每個服務(wù)中包含很多字段币他,這些字段的權(quán)限一般分為讀 read坞靶,寫 write,通知 notiy 幾種蝴悉,就是我們連接設(shè)備后具體需要操作的內(nèi)容彰阴。
Characteristic:特征,GATT 事務(wù)中的最低界別拍冠,Characteristic 是最小的邏輯數(shù)據(jù)單元尿这,當然它可能包含一個組關(guān)聯(lián)的數(shù)據(jù)簇抵,例如加速度計的 X/Y/Z 三軸值。與 Service 類似射众,每個 Characteristic 用 16 bit 或者 128 bit 的 UUID 唯一標識碟摆。每個設(shè)備會提供服務(wù)和特征,類似于服務(wù)端的 API责球,但是機構(gòu)不同焦履。
Description:每個 Characteristic 可以對應(yīng)一個或多個 Description 用戶描述 Characteristic 的信息或?qū)傩浴?/p>
Peripheral、Central:外設(shè)和中心雏逾,發(fā)起連接的是 Central嘉裤,被連接的設(shè)備為 Peripheral。
3.2 工作模式
-
藍牙通信中栖博,首先需要提到的就是 central 和 peripheral 兩個概念屑宠。這是設(shè)備在通信過程中扮演的兩種角色。直譯過來就是 [中心] 和 [周邊(可以理解為外設(shè))]仇让。iOS 設(shè)備既可以作為 central典奉,也可以作為 peripheral,這主要取決于通信需求丧叽。
-
例如在和心率監(jiān)測儀通信的過程中卫玖,監(jiān)測儀作為 peripheral,iOS 設(shè)備作為 central踊淳。區(qū)分的方式即是這兩個角色的重要特點:提供數(shù)據(jù)的是誰假瞬,誰就是 peripheral;需要數(shù)據(jù)的是誰迂尝,誰就是 central脱茉。就像是 client 和 server 之間的關(guān)系一樣。
Bluetooth14
-
-
那怎么發(fā)現(xiàn) peripheral 呢
在 BLE 中垄开,最常見的就是廣播琴许。實際上,peripheral 在不停的發(fā)送廣播溉躲,希望被 central 找到榜田。廣播的信息中包含它的名字等信息。如果是一個溫度調(diào)節(jié)器锻梳,那么廣播的信息應(yīng)該還會包含當前溫度什么的箭券。那么 central 的作用則是去 scan,找到需要連接的 peripheral唱蒸,連接后便可進行通信了邦鲫。
當 central 成功連上 peripheral 后灸叼,它便可以獲取 peripheral 提供的所有 service 和 characteristic神汹。通過對 characteristic 的數(shù)據(jù)進行讀寫庆捺,便可以實現(xiàn) central 和 peripheral 的通信。
-
CoreBluetooth 框架的核心其實是兩個東西屁魏,central 和 peripheral, 對應(yīng)他們分別有一組相關(guān)的 API 和類滔以。
-
這兩組 API 分別對應(yīng)不同的業(yè)務(wù)場景,如下圖氓拼,左側(cè)叫做中心模式你画,就是以你的手機(App)作為中心,連接其他的外設(shè)的場景桃漾。而右側(cè)稱為外設(shè)模式坏匪,使用手機作為外設(shè)連接其他中心設(shè)備操作的場景。
Bluetooth13
-
-
iOS 設(shè)備(App)作為 central 時:
當 central 和 peripheral 通信時撬统,絕大部分操作都在 central 這邊适滓。此時,central 被描述為 CBCentralManager恋追,這個類提供了掃描凭迹、尋找、連接 peripheral(被描述為 CBPeripheral)的方法苦囱。
-
下圖標示了 central 和 peripheral 在 Core Bluetooth 中的表示方式:
Bluetooth16 當你操作 peripheral 的時候嗅绸,實際上是在和它的 service 和 characteristic 打交道,這兩個分別由 CBService 和 CBCharacteristic 表示撕彤。
-
iOS 設(shè)備(App)作為 Peripheral 時:
在 OS X 10.9 和 iOS 6 以后鱼鸠,設(shè)備除了能作為 central 外,還可以作為 peripheral喉刘。也就是說瞧柔,可以發(fā)起數(shù)據(jù),而不像以前只能管理數(shù)據(jù)了睦裳。
那么在此時造锅,它被描述為 CBPeripheralManager,既然是作為 peripheral廉邑,那么這個類提供的主要方法則是對 service 的管理哥蔚,同時還兼?zhèn)渲?central 廣播數(shù)據(jù)的功能。peripheral 同樣會對 central 的讀寫要求做出相應(yīng)蛛蒙。
-
下圖則是設(shè)備作為 central 和 Peripheral 的示意圖:
image 在充當 peripheral 時糙箍,CBPeripheralManager 處理的是可變的 service 和 characteristic,分別由 CBMutableService 和 CBMutableCharacteristic 表示牵祟。
-
中心模式(CBCentralManager)流程:
- 1深夯、建立中心角色
- 2、掃描外設(shè)(discover)
- 3、連接外設(shè)(connect)
- 4咕晋、掃描外設(shè)中的服務(wù)和特征(discover)
- 4.1 獲取外設(shè)的 services
- 4.2 獲取外設(shè)的 Characteristics雹拄,獲取 Characteristics 的值,獲取 Characteristics 的 Descriptor 和 Descriptor 的值
- 5掌呜、與外設(shè)做數(shù)據(jù)交互(explore and interact)
- 6滓玖、訂閱 Characteristic 的通知
- 7、斷開連接(disconnect)
-
外設(shè)模式(CBPeripheralManager)流程:
- 1质蕉、啟動一個 Peripheral 管理對象
- 2势篡、設(shè)置本地 Peripheral 服務(wù)、特性模暗、描述禁悠、權(quán)限等等
- 3、設(shè)置 Peripheral 發(fā)送廣播
- 4兑宇、設(shè)置處理訂閱绷蹲、取消訂閱、讀 characteristic顾孽、寫 characteristic 的委托方法
3.3 服務(wù)祝钢、特征和特征的屬性
一個 peripheral 包含一個或多個 service,或提供關(guān)于信號強度的信息若厚。service 是數(shù)據(jù)和相關(guān)行為的集合拦英。例如,一個心率監(jiān)測儀的數(shù)據(jù)就可能是心率數(shù)據(jù)测秸。
-
service 本身又是由 characteristic 或者其他 service 組成的疤估。characteristic 又提供了更為詳細的 service 信息。還是以心率監(jiān)測儀為例霎冯,service 可能會包含兩個 characteristic铃拇,一個描述當前心率帶的位置,一個描述當前心率的數(shù)據(jù)沈撞。
Bluetooth15 每個 characteristic 屬性分為這么幾種:讀慷荔,寫,通知這么幾種方式缠俺。
-
外設(shè)显晶、服務(wù)、特征間的關(guān)系
Bluetooth1- 一個 CBPeripheral(藍牙設(shè)備) 有一個或者多個 CBService(服務(wù))壹士,而每一個 CBService 有一個或者多個 CBCharacteristic(特征)磷雇,通過可寫的 CBCharacteristic 發(fā)送數(shù)據(jù),而每一個 CBCharacteristic 有一個或者多個 Description 用于描述 characteristic 的信息或?qū)傩浴?/li>
3.4 設(shè)備狀態(tài)
-
藍牙設(shè)備狀態(tài):
- 1躏救、待機狀態(tài)(standby):設(shè)備沒有傳輸和發(fā)送數(shù)據(jù)唯笙,并且沒有連接到任何設(shè)備。
- 2、廣播狀態(tài)(Advertiser):周期性廣播狀態(tài)崩掘。
- 3尿庐、掃描狀態(tài)(Scanner):主動尋找正在廣播的設(shè)備。
- 4呢堰、發(fā)起鏈接狀態(tài)(Initiator):主動向掃描設(shè)備發(fā)起連接。
- 5凡泣、主設(shè)備(Master):作為主設(shè)備連接到其他設(shè)備枉疼。
- 6、從設(shè)備(Slave):作為從設(shè)備連接到其他設(shè)備鞋拟。
-
五種工作狀態(tài):
- 準備(standby)
- 廣播(advertising)
- 監(jiān)聽掃描(Scanning)
- 發(fā)起連接(Initiating)
- 已連接(Connected)
3.5 藍牙和版本的使用限制
藍牙 2.0:越獄設(shè)備
藍牙 4.0:iOS 6 以上
MFi 認證設(shè)備:無限制
3.6 設(shè)置系統(tǒng)使用藍牙權(quán)限
-
設(shè)置系統(tǒng)使用藍牙權(quán)限
Bluetooth2
4骂维、中心模式的使用
中心模式的應(yīng)用場景:主設(shè)備(手機去掃描連接外設(shè),發(fā)現(xiàn)外設(shè)服務(wù)和屬性贺纲,操作服務(wù)和屬性的應(yīng)用航闺。一般來說,外設(shè)(藍牙設(shè)備猴誊,比如智能手環(huán)之類的東西)會由硬件工程師開發(fā)好潦刃,并定義好設(shè)備提供的服務(wù),每個服務(wù)對于的特征懈叹,每個特征的屬性(只讀乖杠,只寫,通知等等)澄成。
藍牙程序需要使用真機調(diào)試胧洒。
4.1 App 連接外設(shè)的實現(xiàn)
-
1、建立中心角色
- 導(dǎo)入 CoreBluetooth 頭文件墨状,建立中心設(shè)備管理類卫漫,設(shè)置主設(shè)備委托。
-
2肾砂、掃描外設(shè)(discover)
掃描外設(shè)的方法需要放在 centralManager 成功打開的代理方法
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
中芒澜,因為只有設(shè)備成功打開,才能開始掃描斩熊,否則會報錯娇妓。掃描到外設(shè)后會進入代理方法
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *, id> *)advertisementData RSSI:(NSNumber *)RSSI;
中。
-
3辫塌、連接外設(shè)(connect)
對要連接的設(shè)備需要進行強引用漏策,否則會報錯。
一個主設(shè)備最多能連 7 個外設(shè)臼氨,每個外設(shè)最多只能給一個主設(shè)備連接掺喻,連接成功,失敗,斷開會進入各自的代理方法中感耙。
-
4褂乍、掃描外設(shè)中的服務(wù)和特征(discover)
- 設(shè)備連接成功后,就可以掃描設(shè)備的服務(wù)了即硼,同樣是通過委托形式逃片,掃描到結(jié)果后會進入委托方法。但是這個委托已經(jīng)不再是主設(shè)備的委托(CBCentralManagerDelegate)只酥,而是外設(shè)的委托(CBPeripheralDelegate),這個委托包含了主設(shè)備與外設(shè)交互的許多回調(diào)方法褥实,包括獲取 services,獲取 characteristics裂允,獲取 characteristics 的值损离,獲取 characteristics 的 Descriptor,和 Descriptor的值绝编,寫數(shù)據(jù)僻澎,讀 RSSI,用通知的方式訂閱數(shù)據(jù)等等十饥。
5 把數(shù)據(jù)寫到 Characteristic 中
6窟勃、訂閱 Characteristic 的通知
7、斷開連接(disconnect)
運行效果
4.2 作為 Central 時的數(shù)據(jù)讀寫
4.2.1 初始化 CBCentralManager
第一步先進行初始化逗堵,可以使用
initWithDelegate:queue:options:
方法:上面的代碼中拳恋,將 self 設(shè)置為代理,用于接收各種 central 事件砸捏。將 queue 設(shè)置為 nil谬运,則表示直接在主線程中運行。
初始化 central manager 之后垦藏,設(shè)置的代理會調(diào)用
centralManagerDidUpdateState:
方法梆暖,所以需要去遵循<CBCentralManagerDelegate>
協(xié)議。這個 did update state 的方法掂骏,能獲得當前設(shè)備是否能作為 central轰驳。關(guān)于這個協(xié)議的實現(xiàn)和其他方法,接下來會講到弟灼,也可以先看看官方 API级解。
4.2.2 搜索當前可用的 peripheral
可以使用 CBCentralManager 的
scanForPeripheralsWithServices:options:
方法來掃描周圍正在發(fā)出廣播的 Peripheral 設(shè)備。第一個參數(shù)為 nil田绑,表示所有周圍全部可用的設(shè)備勤哗。在實際應(yīng)用中,你可以傳入一個 CBUUID 的數(shù)組(注意掩驱,這個 UUID 是 service 的 UUID 數(shù)組)芒划,表示只搜索當前數(shù)組包含的設(shè)備(每個 peripheral 的 service 都有唯一標識 UUID)冬竟。所以,如果你傳入了這樣一個數(shù)組民逼,那么 central manager 則只會去搜素包含這些 service UUID 的 Peripheral泵殴。
CBUUID 是和 peripheral 相關(guān)的,和 central 本身關(guān)系不大拼苍,如果你是做的硬件對接笑诅,那么可以向硬件同事詢問。
在調(diào)用
scanForPeripheralsWithServices:options:
方法之后疮鲫,找到可用設(shè)備吆你,系統(tǒng)會回調(diào)(每找到一個都會回調(diào))centralManager:didDiscoverPeripheral:advertisementData:RSSI:
。該方法會返回找到的 peripheral棚点,所以你可以使用數(shù)組將找到的 peripheral 存起來。當你找到你需要的那個 peripheral 時湾蔓,可以調(diào)用 stop 方法來停止搜索瘫析。
4.2.3 連接 peripheral
找到你需要的 peripheral 之后,下一步就是調(diào)用
connectPeripheral:options:
方法來連接默责。當連接成功后贬循,會回調(diào)方法
centralManager:didConnectPeripheral:
。在這個方法中桃序,你可以去記錄當前的連接狀態(tài)等數(shù)據(jù)杖虾。不過在進行其他操作之前,你應(yīng)該給已連接的這個 peripheral 設(shè)置代理(需要去遵循
<CBPeripheralDelegate>
協(xié)議)媒熊,這樣才能收到 peripheral 的回調(diào)(可以就寫在上面這個方法中)奇适。注意:在連接設(shè)備之前需要對要連接的設(shè)備進行強引用,否則會報錯
4.2.4搜索 peripheral 的 service
當與 peripheral 成功建立連接以后芦鳍,就可以通信了嚷往。第一步是先找到當前 peripheral 提供的 service,因為 service 廣播的數(shù)據(jù)有大小限制(貌似是 31 bytes)柠衅,所以你實際找到的 service 的數(shù)量可能要比它廣播時候說的數(shù)量要多皮仁。調(diào)用 CBPeripheral 的
discoverServices:
方法可以找到當前 peripheral 的所有 service。在實際項目中菲宴,這個參數(shù)應(yīng)該不是 nil 的贷祈,因為 nil 表示查找所有可用的 Service,但實際上喝峦,你可能只需要其中的某幾個势誊。搜索全部的操作既耗時又耗電,所以應(yīng)該提供一個要搜索的 service 的 UUID 數(shù)組谣蠢。
當找到特定的 Service 以后键科,會回調(diào)
<CBPeripheralDelegate>
的peripheral:didDiscoverServices:
方法闻丑。Core Bluetooth 提供了 CBService 類來表示 service,找到以后勋颖,它們以數(shù)組的形式存入了當前 peripheral 的 services 屬性中嗦嗡,你可以在當前回調(diào)中遍歷這個屬性。如果是搜索的全部 service 的話饭玲,你可以選擇在遍歷的過程中侥祭,去對比 UUID 是不是你要找的那個。
4.2.5 搜索 service 的 characteristic
找到需要的 service 之后茄厘,下一步是找它所提供的 characteristic矮冬。如果搜索全部 characteristic,那調(diào)用 CBPeripheral 的
discoverCharacteristics:forService:
方法即可次哈。如果是搜索當前 service 的 characteristic胎署,那還應(yīng)該傳入相應(yīng)的 CBService 對象。同樣是出于節(jié)能的考慮窑滞,第一個參數(shù)在實際項目中應(yīng)該是 characteristic 的 UUID 數(shù)組琼牧。也同樣能在最佳實踐中介紹。
找到所有 characteristic 之后哀卫,回調(diào)
peripheral:didDiscoverCharacteristicsForService:error:
方法巨坊,此時 Core Bluetooth 提供了 CBCharacteristic 類來表示 characteristic〈烁模可以通過以下代碼來遍歷找到的 characteristic趾撵。同樣也可以通過添加 UUID 的判斷來找到需要的 characteristic。
4.2.6 讀取 characteristic 數(shù)據(jù)
characteristic 包含了 service 要傳輸?shù)臄?shù)據(jù)共啃。例如溫度設(shè)備中表達溫度的 characteristic占调,就可能包含著當前溫度值。這時我們就可以通過讀取 characteristic移剪,來得到里面的數(shù)據(jù)妈候。
當找到 characteristic 之后,可以通過調(diào)用 CBPeripheral 的
readValueForCharacteristic:
方法來進行讀取挂滓。當你調(diào)用上面這方法后苦银,會回調(diào)
peripheral:didUpdateValueForCharacteristic:error:
方法,其中包含了要讀取的數(shù)據(jù)赶站。如果讀取正確幔虏,可以用以下方式來獲得值:注意,不是所有 characteristic 的值都是可讀的贝椿,你可以通過 CBCharacteristicPropertyRead options 來進行判斷想括。如果你嘗試讀取不可讀的數(shù)據(jù),那上面的代理方法會返回相應(yīng)的 error烙博。
4.2.7 訂閱 Characteristic 數(shù)據(jù)
其實使用
readValueForCharacteristic:
方法并不是實時的瑟蜈⊙萄罚考慮到很多實時的數(shù)據(jù),比如心率這種铺根,那就需要訂閱 characteristic 了宪躯。可以通過調(diào)用 CBPeripheral 的
setNotifyValue:forCharacteristic:
方法來實現(xiàn)訂閱,注意第一個參數(shù)是 YES位迂。如果是訂閱访雪,成功與否的回調(diào)是
peripheral:didUpdateNotificationStateForCharacteristic:error:
,讀取中的錯誤會以 error 形式傳回掂林。當然也不是所有 characteristic 都允許訂閱臣缀,依然可以通過 CBCharacteristicPropertyNoify options 來進行判斷。
當訂閱成功以后泻帮,那數(shù)據(jù)便會實時的傳回了精置,數(shù)據(jù)的回調(diào)依然和之前讀取 characteristic 的回調(diào)相同(注意,不是訂閱的那個回調(diào))
peripheral:didUpdateValueForCharacteristic:error:
锣杂。
4.2.8 向 characteristic 寫數(shù)據(jù)
寫數(shù)據(jù)其實是一個很常見的需求脂倦,如果 characteristic 可寫,你可以通過 CBPeripheral 類的
writeValue:forCharacteristic:type:
方法來向設(shè)備寫入 NSData 數(shù)據(jù)蹲堂。關(guān)于寫入數(shù)據(jù)的 type狼讨,如上面這行代碼贝淤,type 就是 CBCharacteristicWriteWithResponse柒竞,表示當寫入成功時,要進行回調(diào)播聪。更多的類型可以參考 CBCharacteristicWriteType 枚舉朽基。
如果寫入成功后要回調(diào),那么回調(diào)方法是
peripheral:didWriteValueForCharacteristic:error:
离陶。如果寫入失敗稼虎,那么會包含到 error 參數(shù)返回。注意:characteristic 也可能并不支持寫操作招刨,可以通過 CBCharacteristic 的 properties 屬性來判斷霎俩。
4.3 數(shù)據(jù)讀寫 - 知識補充
4.3.1 CBUUID
CBUUID 對象是用于 BLE 通信中 128 位的唯一標示符。peripheral 的 service沉眶,characteristic打却,characteristic descriptor 都包含這個屬性。這個類包含了一系列生成 UUID 的方法谎倔。
UUID 有 16 位的柳击,也有 128 位的。其中 SIG 組織提供了一部分 16 位的 UUID片习,這部分 UUID 主要用于公共設(shè)備捌肴,例如有個用藍牙連接的心率監(jiān)測儀蹬叭,如果是用的公共的 UUID,那么無論誰做一個 app状知,都可以進行連接秽五,因為它的 UUID 是 SIG 官方提供的,是公開的试幽。如果公司是要做一個只能自己的 app 才能連接的設(shè)備筝蚕,那么就需要硬件方面自定義 UUID。(關(guān)于這方面铺坞,包括通信的 GATT 協(xié)議起宽、廣播流程等詳細介紹,可以看 iOS - GATT Profile 簡介 這篇文章济榨。講得比較詳細坯沪,能在很大程度上幫助我們理解 BLE 通信)。
CBUUID 類提供了可以將 16 位 UUID 轉(zhuǎn)為 128 位 UUID 的方法擒滑。下面的代碼是 SIG 提供的 16 位的心率 service UUID 轉(zhuǎn)為 128 位 UUID 的方法:
如果需要獲取 NSString 形式的 UUID腐晾,可以訪問 CBUUID 的 UUIDString 只讀屬性。
4.3.2 設(shè)備唯一標識符
在有些時候丐一,需要獲取 peripheral 的唯一標示符(比如要做自動連接或綁定用戶等操作)藻糖,但是在搜索到 peripheral 之后,只能拿到 identifier库车,而且這個 identifier 根據(jù)連接的 central 不同而不同巨柒。也就是說,不同的手機連上之后柠衍,identifier 是不同的洋满。雖然比較坑爹,但是這并不影響你做藍牙自動連接珍坊。
唯一標示符(并且不會變的)是設(shè)備的 MAC 地址牺勾,對于 Android 來說,輕輕松松就能拿到阵漏,但對于 iOS驻民,目前這一屬性還是私有的。
-
如果一定有這樣的需求(即一定要使用 MAC 地址)履怯,可以和硬件工程師溝通回还,使用下面的某一種方式解決:
- 將 MAC 地址寫在某一個藍牙特征中,當我們連接藍牙設(shè)備之后虑乖,通過某一個特征獲取 MAC 地址懦趋。
- 將 MAC 地址放在藍牙設(shè)備的廣播數(shù)據(jù)當中,然后在廣播的時候疹味,將 MAC 地址以廣播的形式發(fā)出來仅叫,在不建立連接的情況下帜篇,就能拿到 MAC 地址。
- 我們可以通過藍牙設(shè)備的出廠設(shè)備或者后期手動修改藍牙設(shè)備的 name诫咱,作為唯一標識笙隙。
4.3.3 檢查設(shè)備是否能作為 central
初始化 CBCentralManager 的時候,傳入的 self 代理會觸發(fā)回調(diào)
centralManagerDidUpdateState:
坎缭。在該方法中可通過central.state
來獲得當前設(shè)備是否能作為 central竟痰。state 為 CBManagerState 枚舉類型,具體定義如下:只有當
state == CBManagerStatePoweredOn
時掏呼,才代表正常坏快。
4.3.4 檢查 characteristic 訪問權(quán)限
如果不檢查也沒事,因為無權(quán)訪問會在回調(diào)中返回 error憎夷,但這畢竟是馬后炮莽鸿。如果有需要在讀寫之前檢測,可以通過 characteristic 的 properties 屬性來判斷拾给。該屬性為 CBCharacteristicProperties 的 NS_OPIONS祥得。
多個權(quán)限可以通過
|
和&
來判斷是否支持,比如判斷是否支持讀或?qū)憽?/p>
4.3.5 寫入后是否回調(diào)
在寫入 characteristic 時蒋得,可以選擇是否在寫入后進行回調(diào)级及。調(diào)用方法和枚舉常量如下。
回調(diào)方法為
所以即使沒有判斷寫入權(quán)限额衙,也可以通過回調(diào)的 error 來判斷饮焦,但這樣比起寫入前判斷更耗資源。
4.4 數(shù)據(jù)讀寫 - 最佳實踐
- 在設(shè)備上一般都有很多地方要用到無線電通信入偷,Wi-Fi追驴、傳統(tǒng)的藍牙械哟、以及使用 BLE 通信的 app 等等疏之。這些服務(wù)都是很耗資源的,尤其是在 iOS 設(shè)備上暇咆。所以這里會講解到如何正確的使用 BLE 以達到節(jié)能的效果锋爪。
4.4.1 只掃描你需要的 peripheral
在調(diào)用 CBCentralManager 的
scanForPeripheralsWithServices:options:
方法時,central 會打開無線電去監(jiān)聽正在廣播的 peripheral爸业,并且這一過程不會自動超時其骄。所以需要我們手動設(shè)置 timer 去停掉。如果只需要連接一個 peripheral扯旷,那應(yīng)該在
centralManager:didConnectPeripheral:
的回調(diào)中拯爽,用 stopScan 方法停止搜索。
4.4.2 只在必要的時候設(shè)置 CBCentralManagerScanOptionAllowDuplicatesKey
peripheral 每秒都在發(fā)送大量的數(shù)據(jù)包钧忽,
scanForPeripheralsWithServices:options:
方法會將同一 peripheral 發(fā)出的多個數(shù)據(jù)包合并為一個事件毯炮,然后每找到一個 peripheral 都會調(diào)用centralManager:didDiscoverPeripheral:advertisementData:RSSI:
方法逼肯。另外,當已發(fā)現(xiàn)的 peripheral 發(fā)送的數(shù)據(jù)包有變化時桃煎,這個代理方法同樣會調(diào)用篮幢。以上合并事件的操作是
scanForPeripheralsWithServices:options:
的默認行為,即未設(shè)置 option 參數(shù)为迈。如果不想要默認行為三椿,可將 option 設(shè)置為 CBCentralManagerScanOptionAllowDuplicatesKey。設(shè)置以后葫辐,每收到廣播搜锰,就會調(diào)用上面的回調(diào)(無論廣播數(shù)據(jù)是否一樣)。關(guān)閉默認行為一般用于以下場景:根據(jù) peripheral 的距離來初始化連接(根據(jù)可用信號強度 RSSI 來判斷)耿战。設(shè)置這個 option 會對電池壽命和 app 的性能產(chǎn)生不利影響纽乱,所以一定要在必要的時候,再對其進行設(shè)置昆箕。
4.4.3 正確的搜索 service 與 characteristic
在搜索過程中鸦列,并不是所有的 service 和 characteristic 都是我們需要的,如果全部搜索鹏倘,依然會造成不必要的資源浪費薯嗤。假設(shè)你只需要用到 peripheral 提供的眾多 service 中的兩個,那么在搜索 service 的時候可以設(shè)置要搜索的 service 的 UUID纤泵。
用這種方式搜索到 service 以后骆姐,也可以用類似的辦法來限制 characteristic 的搜索范圍(
discoverCharacteristics:forService:
)。
4.4.4 接收 characteristic 數(shù)據(jù)
-
接收 characteristic 數(shù)據(jù)的方式有兩種:
- 在需要接收數(shù)據(jù)的時候捏题,調(diào)用
readValueForCharacteristic:
玻褪,這種是需要主動去接收的。 - 用
setNotifyValue:forCharacteristic:
方法訂閱公荧,當有數(shù)據(jù)發(fā)送時带射,可以直接在回調(diào)中接收。
- 在需要接收數(shù)據(jù)的時候捏题,調(diào)用
如果 characteristic 的數(shù)據(jù)經(jīng)常變化循狰,那么采用訂閱的方式更好窟社。
4.4.5 適時斷開連接
在不用和 peripheral 通信的時候,應(yīng)當將連接斷開绪钥,這也對節(jié)能有好處灿里。
-
在以下兩種情況下吞杭,連接應(yīng)該被斷開:
- 當 characteristic 不再發(fā)送數(shù)據(jù)時膘魄。(可以通過 isNotifying 屬性來判斷)
- 你已經(jīng)接收到了你所需要的所有數(shù)據(jù)時诅诱。
以上兩種情況划鸽,都需要先結(jié)束訂閱班挖,然后斷開連接桶错。
注意:
cancelPeripheralConnection:
是非阻塞性的履恩,如果在 peripheral 掛起的狀態(tài)去嘗試斷開連接识脆,那么這個斷開操作可能執(zhí)行,也可能不會缕碎。因為可能還有其他的 central 連著它褥影,所以取消連接并不代表底層連接也斷開。從 app 的層面來講咏雌,在決定斷開 peripheral 的時候凡怎,會調(diào)用 CBCentralManagerDelegate 的centralManager:didDisconnectPeripheral:error:
方法。
4.4.6 再次連接 peripheral
-
CoreBluetooth 提供了三種再次連接 peripheral 的方式:
- 調(diào)用
retrievePeripheralsWithIdentifiers:
方法赊抖,重連已知的 peripheral 列表中的 peripheral(以前發(fā)現(xiàn)的统倒,或者以前連接過的)。 - 調(diào)用
retrieveConnectedPeripheralsWithServices:
方法氛雪,重新連接當前【系統(tǒng)】已經(jīng)連接的 peripheral房匆。 - 調(diào)用
scanForPeripheralsWithServices:options:
方法,連接搜索到的 peripheral报亩。
- 調(diào)用
-
是否需要重新連接以前連接過的 peripheral 要取決于你的需求浴鸿,下圖展示了當你嘗試重連時可以選擇的流程:
Bluetooth18 三列代表著三種重連的方式。當然這也是你可以選擇進行實現(xiàn)的弦追,這三種方式也并不是都需要去實現(xiàn)岳链,依然取決于你的需求。
-
1劲件、嘗試連接已知的 peripheral
在第一次成功連上 peripheral 之后掸哑,iOS 設(shè)備會自動給 peripheral 生成一個 identifier(NSUUID 類型),這個標識符可通過 peripheral.identifier 來訪問零远。這個屬性由 CBPeriperal 的父類 CBPeer 提供苗分,API 注釋寫著: The unique, persistent identifier associated with the peer.
因為 iOS 拿不到 peripheral 的 MAC 地址,所以無法唯一標識每個硬件設(shè)備牵辣,根據(jù)這個注釋來看摔癣,應(yīng)該 Apple 更希望你使用這個 identifer 而不是 MAC 地址。值得注意的是服猪,不同的 iOS 連接同一個 peripheral 獲得的 identifier 是不一樣的供填。所以如果一定要獲得唯一的 MAC 地址拐云,可以和硬件工程師協(xié)商罢猪,讓 peripheral 返給你。
當?shù)谝淮芜B接上 peripheral 并且系統(tǒng)自動生成 identifier 之后叉瘩,我們需要將它存下來(可以使用 NSUserDefaults)膳帕。在再次連接的時候,使用
retrievePeripheralsWithIdentifiers:
方法將之前記錄的 peripheral 讀取出來,然后我們?nèi)フ{(diào)用connectPeripheral:options:
方法來進行重新連接危彩。調(diào)用這個方法之后攒磨,會返回一個 CBPeripheral 的數(shù)組,包含了以前連過的 peripheral汤徽。如果這個數(shù)組為空娩缰,則說明沒找到,那么你需要去嘗試另外兩種重連方式谒府。如果這個數(shù)組有多個值拼坎,那么你應(yīng)該提供一個界面讓用戶去選擇。
如果用戶選擇了一個完疫,那么可以調(diào)用
connectPeripheral:options:
方法來進行連接泰鸡,連接成功之后依然會走centralManager:didConnectPeripheral:
回調(diào)。-
注意壳鹤,連接失敗通常有一下幾個原因:
- peripheral 與 central 的距離超出了連接范圍盛龄。
- 有一些 BLE 設(shè)備的地址是周期性變化的。所以芳誓,即使 peripheral 就在旁邊余舶,如果它的地址已經(jīng)變化,而你記錄的地址已經(jīng)變化了锹淌,那么也是連接不上的欧芽。如果是因為這種原因連接不上,那你需要調(diào)用
scanForPeripheralsWithServices:options:
方法來進行重新搜索葛圃。
更多關(guān)于隨機地址的資料可以看 《蘋果產(chǎn)品的藍牙附件設(shè)計指南》千扔。
-
2、連接系統(tǒng)已經(jīng)連接過的 peripheral
另外一種重連的方式是通過檢測當前系統(tǒng)是否已經(jīng)連上了需要的 peripheral(可能被其他 app 連接了)库正。調(diào)用
retrieveConnectedPeripheralsWithServices:
會返回一個 CBPeripheral 的數(shù)組曲楚。因為當前可能不止一個 peripheral 連上的,所以你可以通過傳入一個 service 的 CBUUID 的數(shù)組來過濾掉一些不需要的 peripheral褥符。同樣龙誊,這個數(shù)組有可能為空,也有可能不為空喷楣,處理方式和上一節(jié)的方式相同趟大。找到要連接的 peripheral 之后,處理方式也和上一節(jié)相同铣焊。
4.4.7 自動連接
可以在程序啟動或者需要使用藍牙的時候逊朽,判斷是否需要自動連接。如果需要曲伊,則可以嘗試連接已知的 peripheral叽讳。這個重連上一個小節(jié)剛好提到過:在上一次連接成功后,記錄 peripheral 的 identifier,然后重連的時候岛蚤,讀取即可邑狸。
在自動連接這一塊,還有一個小坑涤妒。在使用
retrievePeripheralsWithIdentifiers:
方法將之前記錄的 peripheral 讀取出來单雾,然后我們?nèi)フ{(diào)用connectPeripheral:options:
方法來進行重新連接。我之前怎么試都有問題她紫,最后在 CBCentralManager 的文檔上找到了這樣一句話:Pending connection attempts are also canceled automatically when peripheral is deallocated.這句話的意思是說铁坎,在 peripheral 的引用釋放之后,連接會自動取消犁苏。因為我在讀取出來之后硬萍,接收的 CBPeripheral 是臨時變量,沒有強引用围详,所以出了作用域就自動釋放了朴乖,從而連接也自動釋放了。所以在自動連接的時候助赞,讀取出來別忘了去保存引用买羞。
4.4.8 連接超時
- 因為 CoreBluetooth 并未幫我們處理連接超時相關(guān)的操作,所以超時的判斷還需要自己維護一個 timer雹食⌒笃眨可以在 start scan 的時候啟動(注意如果是自動連接,那么重連的時候也需要啟動)群叶,然后在搜索到以后 stop timer吃挑。當然,如果超時街立,則看你具體的處理方式了舶衬,可以選擇 stop scan,然后讓用戶手動刷新赎离。
4.4.9 藍牙名稱更新
在 peripheral 修改名字過后逛犹,iOS 存在搜索到藍牙名字還未更新的問題。先來說一下出現(xiàn)這個問題的原因梁剔,以下是摘自 Apple Developer Forums 上的回答:
There are 2 names to consider. The advertising name and the GAP (Generic Access Profile) name.
For a peripheral which iOS has never connected before, the ‘name’ property reported is the advertising name. Once it is connected, the GAP name is cached, and is reported as the peripheral’s name. GAP name is considered a “better” name due to the size restrictions on the advertising name.
There is no rule that says both names must match. That depends on your use case and implementation. Some people will consider the GAP name as the fixed name, but the advertising name more of an “alias”, as it can easily be changed.
If you want both names in sync, you should change the GAP name as well along with the advertised name. Implemented properly, your CB manager delegate will receive a call to – peripheralDidUpdateName:
If you want to manually clear the cache, you need to reset the iOS device.
大致意思是:peripheral 其實存在兩個名字虽画,一個 advertising name,一個 GAP name荣病。在沒有連接過時码撰,收到的 CBPeripheral 的 name 屬性是 advertising name(暫且把這個名字稱為正確的名字,因為在升級或換名字之后众雷,這個名字才是最新的)灸拍。一旦 iOS 設(shè)備和 peripheral 連接過做祝,GAP name 就會被緩存砾省,與此同時鸡岗,CBPeripheral 的 name 屬性變成 GAP name,所以在搜索到設(shè)備時编兄,打印 CBPeripheral 的 name轩性,怎么都沒有變。上文給出的解釋是狠鸳,因為數(shù)據(jù)大小限制揣苏,GAP name 更優(yōu)于 advertising name。這兩個名字不要求要相同件舵,并且卸察,如果要清除 GAP name 的緩存,那么需要重置 iOS 設(shè)備铅祸。
下面來說一下解決方案坑质,主要分為兩種,一種是更新 GAP name临梗,一種是直接拿 advertising name涡扼。
更新 GAP name 的方式我目前沒找到方法,有些人說是 Apple 的 bug盟庞,這個還不清楚吃沪,希望有解決方案的朋友聯(lián)系我。
那就來說下怎么拿到 advertising name 吧什猖。
centralManager:didDiscoverPeripheral:advertisementData:RSSI:
方法中可以通過 advertisementData 來拿到 advertising name票彪,如下:
objc NSLog(@"%@", advertisementData[CBAdvertisementDataLocalNameKey]);
然后可以選擇把這個 name 返回外部容器來進行顯示,用戶也可以通過這個來進行選擇不狮。
-
關(guān)于這個部分查找的資料有:
4.5 數(shù)據(jù)讀寫 - OTA 固件升級與文件傳輸
OTA(Over-the-Air):空中傳輸抹镊,一般用于固件升級,網(wǎng)上的資料大多是怎么給手機系統(tǒng)升級荤傲,少部分資料是 peripheral 怎么接收并進行升級垮耳,唯獨沒有 central 端怎么傳輸?shù)摹F鋵嵨募鬏敽芎唵嗡焓颍皇撬{牙傳輸?shù)臄?shù)據(jù)大小使得這一步驟稍顯復(fù)雜终佛。
首先,文件傳輸雾家,其實也是傳輸?shù)臄?shù)據(jù)铃彰,即 NSData,和普通的 peripheral 寫入沒什么區(qū)別芯咧。固件升級的文件一般是
.bin
文件牙捉,也有.zip
的竹揍。不過這些文件,都是數(shù)據(jù)邪铲,所以首先將文件轉(zhuǎn)為 NSData芬位。但是 data 一般很長,畢竟是文件带到。直接通過
writeValue:forCharacteristic:type:
寫入的話昧碉,不會有任何回調(diào)。哪怕是錯誤的回調(diào)揽惹,都沒有被饿。這是因為藍牙單次傳輸?shù)臄?shù)據(jù)大小是有限制的。具體的大小我不太明確搪搏,看到 StackOverflow 上有人給出的 20 bytes狭握,我就直接用了,并沒有去具體查證(不過試了試 30 bytes疯溺,回調(diào)數(shù)據(jù)長度錯誤)论颅。既然長度是 20,那在每次發(fā)送成功的回調(diào)中喝检,再進行發(fā)送就好嗅辣,直到發(fā)送完成。下面來討論下是怎么做的吧挠说。
-
1澡谭、區(qū)別普通寫入與文件寫入
分割數(shù)據(jù)并發(fā)送,每次都要記錄上一次已經(jīng)寫入長度(偏移量 self.otaSubDataOffset)损俭,然后截取 20 個長度蛙奖。需要注意的是最后一次的長度,注意不要越界了杆兵。
數(shù)據(jù)的發(fā)送和普通寫入沒什么區(qū)別雁仲。
-
2、當前已發(fā)送長度與發(fā)送結(jié)束的回調(diào)
因為 OTA 的寫入可能需要做進度條之類的琐脏,所以最好和普通的寫入回調(diào)區(qū)分開攒砖。
在每次寫入成功中,判斷是否已經(jīng)發(fā)送完成(已發(fā)送的長度和總長度相比)日裙。如果還未發(fā)送完成吹艇,則返回已發(fā)送的長度給控制器(可以通過代理實現(xiàn))。如果已發(fā)送完成昂拂,則返回發(fā)送完成(可以通過代理實現(xiàn))受神。
繼續(xù)查看 iOS - Bluetooth 藍牙介紹(下)