概述
公司的項(xiàng)目是醫(yī)療類的項(xiàng)目,所以這段一直在和藍(lán)牙打交道惯吕。我使用的是蘋果原生的框架CoreBluetooth惕它。在對(duì)接幾個(gè)藍(lán)牙設(shè)備的過(guò)程中,也遇到一些坑废登,下文我會(huì)一一列舉淹魄。 git上有個(gè)庫(kù)BabyBluetooth 基于原生CoreBluetooth框架進(jìn)行了封裝,使用起來(lái)也很方便钳宪,大家可以嘗試一下揭北。 那么我們開始吧!
正文
在了解下文內(nèi)容之前吏颖,我已默認(rèn)你已經(jīng)了解一些基本概念:
- 什么是中心設(shè)備
- 什么是外圍設(shè)備
- 什么是服務(wù)(service)
- 什么是特性(characteristic)
- 什么是訂閱(notify)
- 什么是UUID
...
基本了解了以上一些概念搔体,下面的內(nèi)容將比較好理解。
**
需要注明半醉,下面的UUID是我的藍(lán)牙設(shè)備中的Service和Characteristic的UUID疚俱,要注意根據(jù)自己的藍(lán)牙
設(shè)備提供的Service和Characteristic的UUID來(lái)替換
**
// 藍(lán)牙設(shè)備提供的服務(wù)的UUID
#define kCGMServiceTwoUUID @"0000FFF0-0000-1000-8000-00805F9B34FB"
// 藍(lán)牙設(shè)備提供的寫入特性
#define kCGMCharacteristicOneUUID @"0000FFF1-0000-1000-8000-00805F9B34FB"
// 藍(lán)牙設(shè)備提供的notify特性
#define kCGMCharacteristicTwoUUID @"0000FFF2-0000-1000-8000-00805F9B34FB"
那么,先讓我們了解下藍(lán)牙交互流程中幾個(gè)常用的回調(diào)缩多。
- 中心設(shè)備CBCentralManager更新設(shè)備藍(lán)牙狀態(tài)的回調(diào)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state) {
case CBCentralManagerStatePoweredOn:
{
// 掃描外圍設(shè)備
[self.centeralManager scanForPeripheralsWithServices:nil options:nil];
}
break;
default:
NSLog(@"設(shè)備藍(lán)牙未開啟");
break;
}
}
- 中心設(shè)備已經(jīng)發(fā)現(xiàn)外圍設(shè)備回調(diào)
**
這里有幾個(gè)問題值得注意:
**
1. 在ios中藍(lán)牙廣播信息中通常會(huì)包含以下4種類型的信息呆奕。ios的藍(lán)牙通信協(xié)議中不接受其他類型的廣播信息。因此需要注意的是衬吆,如果需要在掃描設(shè)備時(shí)梁钾,通過(guò)藍(lán)牙設(shè)備的Mac地址來(lái)唯一辨別設(shè)備,那么需要與藍(lán)牙設(shè)備的硬件工程師溝通好:將所需要的Mac地址放到一下幾種類型的廣播信息中逊抡。通常放到kCBAdvDataManufacturerData這個(gè)字段中姆泻。
kCBAdvDataIsConnectable = 1;
kCBAdvDataLocalName = XXXXXX;
kCBAdvDataManufacturerData = <XXXXXXXX>;
kCBAdvDataTxPowerLevel = 0;
2. 設(shè)備的UUID(peripheral.identifier)是由兩個(gè)設(shè)備的mac通過(guò)算法得到的,所以不同的手機(jī)連接相同的設(shè)備冒嫡,它的UUID都是不同的拇勃,無(wú)法標(biāo)識(shí)設(shè)備。
3. 蘋果與藍(lán)牙設(shè)備連接通信時(shí)孝凌,使用的并不是蘋果藍(lán)牙模塊的Mac地址方咆,使用的是蘋果隨機(jī)生成的十六進(jìn)制碼作為手機(jī)藍(lán)牙的Mac與外圍藍(lán)牙設(shè)備進(jìn)行交互。如果藍(lán)牙設(shè)備與手機(jī)在一定時(shí)間內(nèi)多次通信蟀架,那么使用的是首次連接時(shí)隨機(jī)生成的十六進(jìn)制碼作為Mac地址瓣赂,超過(guò)這個(gè)固定的時(shí)間段榆骚,手機(jī)會(huì)清空已隨機(jī)生成的Mac地址,重新生成钩述。也就是說(shuō)外圍設(shè)備是不能通過(guò)與蘋果手機(jī)的交互時(shí)所獲取的藍(lán)牙Mac地址作為手機(jī)的唯一標(biāo)識(shí)的寨躁。(這是在與寫藍(lán)牙設(shè)備的固件工程師聯(lián)調(diào)時(shí)根據(jù)問題的現(xiàn)象推測(cè)的。至于蘋果藍(lán)牙通訊協(xié)議的底層是否確實(shí)完全像我所說(shuō)的這樣牙勘,希望了解的讀者能提供幫助。在此先謝過(guò)所禀。)
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
NSLog(@"advertisementData.kCBAdvDataManufacturerData = %@", advertisementData[@"kCBAdvDataManufacturerData"]);
_connectPeripheral = peripheral;
// [self.centeralManager connectPeripheral:peripheral options:nil];
if ([advertisementData[@"kCBAdvDataLocalName"] hasPrefix:@"SN"]){
NSLog(@"已搜索到設(shè)備");
NSLog(@"peripheral.identifier = %@ peripheral.name = %@", peripheral.identifier, peripheral.name);
[_delegate getAdvertisementData:advertisementData andPeripheral:peripheral];
[_peripheralArray addObject:peripheral];
}
}
- 中心設(shè)備設(shè)備連接成功回調(diào)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
// 設(shè)備停止掃描
[self.centeralManager stopScan];
peripheral.delegate = self;
dispatch_after(2, dispatch_get_main_queue(), ^{
// 查找服務(wù)
[_connectPeripheral discoverServices:@[[CBUUID UUIDWithString:kCGMServiceTwoUUID]]];
});
}
- 中心設(shè)備設(shè)備連接失敗回調(diào)
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
[_operationDelegate failToConnect];
}
- 中心設(shè)備設(shè)備連接中斷回調(diào)
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"連接斷開 %@", [error localizedDescription]);
[_operationDelegate disconnected];
}
- 外圍設(shè)備(CBPeripheral)發(fā)現(xiàn)服務(wù)(service)回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (error) {
// 輸出錯(cuò)誤信息
NSLog(@"discoverServices.error============ %@", [error localizedDescription]);
return;
}
// 遍歷設(shè)備提供的服務(wù)
for (CBService *service in peripheral.services) {
NSLog(@"service.UUID = ------------- = %@", service.UUID.UUIDString);
// 找到需要的服務(wù)方面,并獲取該服務(wù)響應(yīng)的特性
if([service.UUID isEqual:[CBUUID UUIDWithString:kCGMServiceTwoUUID]]) {
[service.peripheral discoverCharacteristics:nil forService:service];
NSLog(@"開始查找cgm的characteristic");
}
}
}
- 外圍設(shè)備發(fā)現(xiàn)特性(characteristic)回調(diào)
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error) {
// 輸出錯(cuò)誤信息
NSLog(@"discoverCharacteristics.error=========== %@", [error localizedDescription]);
return;
}
// 遍歷服務(wù)中的所有特性
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicOneUUID]]) {
// 設(shè)置讀寫的特性
_readAndWriteCharacteristic = characteristic;
} else if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicTwoUUID]]) {
// 設(shè)置需要訂閱的特性
_notifyCharacteristic = characteristic;
[_connectPeripheral setNotifyValue:YES forCharacteristic:_notifyCharacteristic];
}
}
}
- 外圍設(shè)備數(shù)據(jù)更新回調(diào), 可以在此回調(diào)方法中讀取信息(無(wú)論是read的回調(diào)色徘,還是notify(訂閱)的回調(diào)都是此方法)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
// 輸出錯(cuò)誤信息
NSLog(@"didupadteValueForCharacteristic error ============ %@", [error localizedDescription]);
return;
}
NSLog(@"value ============= %@", characteristic.value);
// 解析數(shù)據(jù)
NSData *data = characteristic.value;
// 將NSData轉(zhuǎn)Byte數(shù)組
NSUInteger len = [data length];
Byte *byteData = (Byte *)malloc(len);
memcpy(byteData, [data bytes], len);
NSMutableArray *commandArray = [NSMutableArray arrayWithCapacity:0];
// Byte數(shù)組轉(zhuǎn)字符串
for (int i = 0; i < len; i++) {
NSString *str = [NSString stringWithFormat:@"%02x", byteData[i]];
[commandArray addObject:str];
NSLog(@"byteData = %@", str);
}
// 輸出數(shù)據(jù)
[_operationDelegate dataWithCharacteristic:commandArray];
}
- 特性已寫入外圍設(shè)備的回調(diào)(如果寫入類型為CBCharacteristicWriteWithResponse 回調(diào)此方法恭金,如果寫入類型為CBCharacteristicWriteWithoutResponse不回調(diào)此方法)
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
NSLog(@"write.error=======%@",error.userInfo);
}
/* When a write occurs, need to set off a re-read of the local CBCharacteristic to update its value */
// 讀數(shù)據(jù)
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicOneUUID]]) {
[self readCharacter];
}
}
- 外圍設(shè)備訂閱特征值狀態(tài)改變成功的回調(diào)
需要注意的是這里是對(duì)kCGMCharacteristicOneUUID這個(gè)特性進(jìn)行寫入,這里之所以這樣操作是因?yàn)槲业乃{(lán)牙設(shè)備的藍(lán)牙協(xié)議是這樣定義的褂策,所以這里不要照抄照搬横腿,要按照你的藍(lán)牙設(shè)備的通訊協(xié)議來(lái)確定,對(duì)哪一個(gè)特性進(jìn)行read斤寂,對(duì)哪個(gè)特性進(jìn)行write耿焊,以及對(duì)哪個(gè)特性進(jìn)行設(shè)置Notify
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
if (error) {
NSLog(@"error = %@", [error localizedDescription]);
}
// 對(duì)特性kCGMCharacteristicTwoUUID設(shè)置notify(訂閱),成功以后回調(diào)
if ([characteristic.UUID.UUIDString isEqualToString:kCGMCharacteristicTwoUUID] && characteristic.isNotifying) {
// 寫數(shù)據(jù) 回調(diào)-didWriteValueForCharacteristic
NSLog(@"寫數(shù)據(jù)到cgm設(shè)備的characteristic = %@", _readAndWriteCharacteristic.UUID.UUIDString);
[_operationDelegate writeCharacteristic];
}
}
另外遍搞,除了回調(diào)以外罗侯,還有幾個(gè)點(diǎn)需要注意:
- 搜索外圍設(shè)備
- (void)searchlinkDevice
{
// 實(shí)現(xiàn)代理
// 掃描設(shè)備
// _centeralManager = [[CBCentralManager alloc] initWithDelegate:self
// queue:nil];
if(self.centeralManager.state == CBCentralManagerStatePoweredOff) {
// 藍(lán)牙關(guān)閉的
} else if(self.centeralManager.state == CBCentralManagerStateUnsupported) {
// 設(shè)備不支持藍(lán)牙
} else if(self.centeralManager.state == CBCentralManagerStatePoweredOn ||
self.centeralManager.state == CBCentralManagerStateUnknown) {
// 開啟的話開始掃描藍(lán)牙設(shè)備
[self.centeralManager scanForPeripheralsWithServices:nil options:nil];
double delayInSeconds = 20.0;
// 掃描20s后未掃描到設(shè)備停止掃描
dispatch_time_t popTime =
dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime,
dispatch_get_main_queue(),
^(void) {
[self stopScan];
});
}
}
- 對(duì)某個(gè)特性(characteristic)寫入數(shù)據(jù)
- (void)writeCharacter:(NSData *)data
{
NSLog(@" characteristic.uuid = %@ data ==== %@", _readAndWriteCharacteristic.UUID.UUIDString, data);
if ([_readAndWriteCharacteristic.UUID isEqual:[CBUUID UUIDWithString:kCGMCharacteristicOneUUID]]) {
[_connectPeripheral writeValue:data forCharacteristic:_readAndWriteCharacteristic type:CBCharacteristicWriteWithResponse];
} else {
[_connectPeripheral writeValue:data
forCharacteristic:_readAndWriteCharacteristic
type:CBCharacteristicWriteWithoutResponse];
}
}
- 讀數(shù)據(jù)
需要注意的是這里讀取藍(lán)牙信息 (但并不是在返回值中接收,要在
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error;
這個(gè)回調(diào)方法中接收)
- (void)readCharacter
{
[_connectPeripheral readValueForCharacteristic:_readAndWriteCharacteristic];
}