目前iOS智能硬件的開發交互方式主要分為兩種,一種是基於低功耗的藍牙4.0技術(由於耗電低,也稱作為BLE(Bluetooth Low Energy))對應iOS的框架為CoreBluetooth,另外一種是基於Wi-Fi的連接方式,由於之前做過的兩個項目用到的都是藍牙,所以下面主要是介紹藍牙4.0技術。
對應的在項目中添加的藍牙開發權限
Important An iOS app linked on or after iOS 10.0 must include in its Info.plist file the usage description keys for the types of data it needs to access or it will crash. To access Bluetooth peripheral data specifically, it must include NSBluetoothPeripheralUsageDescription.
在iOS中用於藍牙開發的框架是CoreBluetooth,里面主要有以下幾個核心概念:
CBCentralManager: 外部設備管理者
CBPeripheral: 連接的外部設備
CBService: 設備攜帶的服務
CBCharacteristic: 服務中包含的特征值
藍牙的開發分兩種模式,一種是app作為主設備,掃描連接其他外部藍牙設備,另外一種是app作為外部設備被主設備連接。
目前開發的兩個項目中主要用到的是第一種模式。
下面講解一下第一種模式的流程:
1、首先先創建CBCentralManager對象,初始化完設置相應的代理對象之后就會有一個CBCentralManager的回調方法過來。
2、根據CBCentralManager初始化完之后的回調方法判斷藍牙目前的相應狀態,如果處於開啟狀態則開始掃描包含你所需要的服務的外設。
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{ switch (central.state) { case CBManagerStateUnknown: NSLog(@"CBManagerStateUnknown"); break; case CBManagerStateResetting: NSLog(@"CBManagerStateResetting"); break; case CBManagerStateUnsupported: NSLog(@"CBManagerStateUnsupported"); break; case CBManagerStateUnauthorized: NSLog(@"CBManagerStateUnauthorized"); break; case CBManagerStatePoweredOff: NSLog(@"CBManagerStatePoweredOff"); break; case CBManagerStatePoweredOn: NSLog(@"CBManagerStatePoweredOn"); //開始掃描周圍的外設 /* 第一個參數nil就是掃描周圍所有的外設,掃描到外設后會進入 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI; 第二個參數可以添加一些option,來增加精確的查找范圍, 如 : NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], CBCentralManagerScanOptionAllowDuplicatesKey, nil]; [manager scanForPeripheralsWithServices:nil options:options]; */ [central scanForPeripheralsWithServices:nil options:nil]; break; default: break; } }
3、掃面發現需要的外圍設備
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{ //連接外圍設備
//advertisementData 外圍設備廣播的信息,一般會在kCBAdvDataLocalName這個地方放置設備的mac地址,如果安卓收得到iOS設備收不到,一般來說是因為硬件設備沒有調試好,可以找硬件工程師師調試
if ([peripheral.name isEqualToString:@"外設名字"] && peripheral) { [self.centralManager connectPeripheral:peripheral options:nil]; } }
4、連接外部設備
//連接到外圍設備 - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{ NSLog(@"連接外圍設備成功"); //設置外圍設備的代理為當前視圖控制器 peripheral.delegate = self; //外圍設備開始尋找服務 [peripheral discoverServices:@[[CBUUID UUIDWithString:kServiceUUID]]]; } //連接外圍設備失敗 - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{ NSLog(@"連接外圍設備失敗!"); }
5、發現外部設備的服務、發現外部設備的服務對應的特征值
//外圍設備尋找到服務后 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{ NSLog(@"已發現可用服務..."); if (error) { NSLog(@"外圍設備尋找服務過程中發生錯誤,錯誤信息:%@",error.localizedDescription); } //遍歷查找到的服務 CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID]; CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID]; for (CBService *service in peripheral.services){ if ([service.UUID isEqual:serviceUUID]) { //外圍設備查找指定服務中的特征 [peripheral discoverCharacteristics:@[characteristicUUID] forService:service]; } } } //外圍設備尋找到特征后 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{ NSLog(@"已發現可用特征...."); if (error) { NSLog(@"外圍設備尋找特征過程中發生錯誤,錯誤信息:%@",error.localizedDescription); } //遍歷服務中的特征 CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID]; CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID]; if ([service.UUID isEqual:serviceUUID]) { for (CBCharacteristic *characteristic in service.characteristics){ if ([characteristic.UUID isEqual:characteristicUUID]) { //情景一:通知 /*找到特征后設置外圍設備為已通知狀態(訂閱特征): 1.調用此方法會觸發代理方法-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error 2.調用此方法會觸發外圍設備的訂閱代理方法 */ [peripheral setNotifyValue:YES forCharacteristic:characteristic]; //情景二:讀取 // [peripheral readValueForCharacteristic:characteristic]; // if (characteristic.value) { // NSString *value = [[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]; // NSLog(@"讀取到特征值:%@",value); // } } } } }
6、訂閱特征值的狀態發生更新
//特征值被更新后 - (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ NSLog(@"收到特征更新通知..."); if (error) { NSLog(@"更新通知狀態時發生錯誤,錯誤信息:%@",error.localizedDescription); } //給特征值設置新的值 CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID]; if ([characteristic.UUID isEqual:characteristicUUID]) { if (characteristic.isNotifying) { if (characteristic.properties == CBCharacteristicPropertyNotify) { NSLog(@"已訂閱特征通知."); return; }else if (characteristic.properties == CBCharacteristicPropertyRead){ //從外圍設備讀取新值,調用此方法會觸發代理方法:-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error [peripheral readValueForCharacteristic:characteristic]; } }else{ NSLog(@"停止已停止."); //取消連接 [self.centralManager cancelPeripheralConnection:peripheral]; } } }
7、特征值發生變化時的回調方法
//更新特征值后(調用readValueForCharacteristic:方法或者外圍設備在訂閱后更新特征值都會調用此代理方法) - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{ if (error) { NSLog(@"更新特征值時發生錯誤,錯誤信息:%@",error.localizedDescription); return; } if (characteristic.value) { NSString *value = [[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding]; NSLog(@"讀取到特征值:%@",value); }else{ NSLog(@"未發現特征值."); } }
8、往藍牙設備寫入數據
- (void)writeDataWithHexStr:(NSString *)hexStr { NSData *data = [self convertHexStrToData:hexStr]; [self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse]; } // 16進制轉NSData - (NSData *)convertHexStrToData:(NSString *)str { if (!str || [str length] == 0) { return nil; } NSMutableData *hexData = [[NSMutableData alloc] initWithCapacity:20]; NSRange range; if ([str length] % 2 == 0) { range = NSMakeRange(0, 2); } else { range = NSMakeRange(0, 1); } for (NSInteger i = range.location; i < [str length]; i += 2) { unsigned int anInt; NSString *hexCharStr = [str substringWithRange:range]; NSScanner *scanner = [[NSScanner alloc] initWithString:hexCharStr]; [scanner scanHexInt:&anInt]; NSData *entity = [[NSData alloc] initWithBytes:&anInt length:1]; [hexData appendData:entity]; range.location += range.length; range.length = 2; } return hexData; }
9、關閉藍牙設備
- (void)closeBlueTooth { [self.centralManager stopScan]; if (self.peripheral) { [self.centralManager cancelPeripheralConnection:self.peripheral]; } self.centralManager = nil; self.peripheral = nil; self.characteristic = nil; }
10、對應的還有swift版藍牙相關的常用工具類方法
//MARK: - 工具類方法 //16進制字符串轉化為data class func HexStrToData(hexStr:String)->Data { assert(hexStr.count % 2 == 0, "輸入字符串格式不對,8位代表一個字符") var bytes = [UInt8]() var sum = 0 // 整形的 utf8 編碼范圍 let intRange = 48...57 // 小寫 a~f 的 utf8 的編碼范圍 let lowercaseRange = 97...102 // 大寫 A~F 的 utf8 的編碼范圍 let uppercasedRange = 65...70 for (index, c) in hexStr.utf8CString.enumerated() { var intC = Int(c.byteSwapped) if intC == 0 { break } else if intRange.contains(intC) { intC -= 48 } else if lowercaseRange.contains(intC) { intC -= 87 } else if uppercasedRange.contains(intC) { intC -= 55 } else { assertionFailure("輸入字符串格式不對,每個字符都需要在0~9,a~f,A~F內") } sum = sum * 16 + intC // 每兩個十六進制字母代表8位,即一個字節 if index % 2 != 0 { bytes.append(UInt8(sum)) sum = 0 } } let data = Data(bytes: bytes) return data } //16進制Data 轉 String class func string(from data:Data)->String { return data.map { String(format: "%02x", $0) } .joined(separator: "") } // MARK: - 十進制轉二進制 class func decTobin(number:Int) -> String { var num = number var str = "" while num > 0 { str = "\(num % 2)" + str num /= 2 } return str } // MARK: - 二進制轉十進制 class func binTodec(number num: String) -> Int { var sum: Int = 0 for c in num { let str = String(c) sum = sum * 2 + Int(str)! } return sum } // MARK: - 十進制轉十六進制 class func decTohex(number:Int) -> String { return String(format: "%0X", number) } // MARK: - 十六進制轉十進制 class func hexTodec(number num:String) -> Int { let str = num.uppercased() var sum = 0 for i in str.utf8 { sum = sum * 16 + Int(i) - 48 // 0-9 從48開始 if i >= 65 { // A-Z 從65開始,但有初始值10,所以應該是減去55 sum -= 7 } } return sum }