BLE中心模式流程-coding
BLE中心模式流程
- 1.建立中心角色
- 2.掃描外設(Discover Peripheral)
- 3.連接外設(Connect Peripheral)
- 4.掃描外設中的服務和特征(Discover Services And Characteristics)
- 4.1 獲取外設的services
- 4.2 獲取外設的Characteristics,獲取characteristics的值,,獲取Characteristics的Descriptor和Descriptor的值
- 5.利用特征與外設做數(shù)據(jù)交互(Explore And Interact)
- 6.訂閱Characteristic的通知
- 7.斷開連接(Disconnect)
準備環(huán)境
- 1.Xcode
- 2.下載藍牙調試助手
- 3.外設
實現(xiàn)步驟
#define UUID0 @"FFE0" // 外設服務
#define UUID1 @"FFE1" // 外設特征值(write)
#define UUID2 @"FFF2" // 外設特征值(read)
#define UUID3 @"FFF3" // 外設特征值(notify)
#define UUID4 @"FFF4" // 外設特征值(掃描過濾)
導入CB頭文件,建立主設備管理類,設置主設備代理
#import <CoreBluetooth/CoreBluetooth.h>
@property (nonatomic, strong) CBCentralManager *cMgr; /**< 中心管理設備 */
- (CBCentralManager *)cMgr {
if (!_cMgr) {
_cMgr = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()]; // 線程不傳默認是主線程
}
return _cMgr;
}
- 設備藍牙狀態(tài)更新回調
#pragma mark 藍牙狀態(tài)更新回調
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
LogFunc
self.peripheralState = central.state;
switch (central.state) {
case CBManagerStateUnknown: {
NSLog(@"為知狀態(tài)");
}
break;
case CBManagerStateResetting: {
NSLog(@"重置狀態(tài)");
}
break;
case CBManagerStateUnsupported: {
NSLog(@"不支持的狀態(tài)");
}
break;
case CBManagerStateUnauthorized: {
NSLog(@"未授權的狀態(tài)");
}
break;
case CBManagerStatePoweredOff: {
NSLog(@"關閉狀態(tài)");
if ([self.delegate respondsToSelector:@selector(bluetoothTool:didDisconnectPeripheral:error:)]) {
[self.delegate bluetoothTool:self didDisconnectPeripheral:nil error:nil];
}
self.peripheral = nil;
self.writeCharacteristic = nil;
self.notifyCharacteristic = nil;
self.readCharacteristic = nil;
}
break;
case CBManagerStatePoweredOn: {
NSLog(@"開啟狀態(tài)-可用狀態(tài)");
self.peripheralState = central.state;
if ([self.delegate respondsToSelector:@selector(powerOn:)]) {
[self.delegate powerOn:self];
}
}
break;
default:
break;
}
}
- 開始掃描:
// 傳nil會掃描所有設備校辩,可以根據(jù)需求傳入你們硬件給的過濾的UDID
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
- 發(fā)現(xiàn)外設
#pragma mark 發(fā)現(xiàn)外設
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
// 連接外設球订,獲取廣播數(shù)據(jù),比如mac地址等等
self.peripheral = peripheral;
[self.centralManager connectPeripheral:peripheral options:nil];
NSData *adata = advertisementData[@"kCBAdvDataManufacturerData"];
NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[adata length]];
[adata enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
unsigned char *dataBytes = (unsigned char*)bytes;
for (NSInteger i = 0; i < byteRange.length; i++) {
NSString *hexStr = [NSString stringWithFormat:@"%x", (dataBytes[i]) & 0xff];
if ([hexStr length] == 2) {
[string appendString:hexStr];
} else {
[string appendFormat:@"0%@", hexStr];
}
}
}];
}
}
連接成功
#pragma mark 連接成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
LogFunc
NSLog(@"連接成功");
// 停止掃描
[self.centralManager stopScan];
self.peripheral = peripheral;
self.peripheral.delegate = self;
CBUUID *uuid = [CBUUID UUIDWithString:UUID0];
NSArray *serives = @[uuid];
[peripheral discoverServices:serives];
}
#pragma mark 連接失敗
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
LogFunc;
NSLog(@"連接失敗 %@", error);
}
#pragma mark 連接斷開
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
LogFunc
NSLog(@"連接斷開了 %@", error);
self.peripheral = nil;
self.writeCharacteristic = nil;
self.notifyCharacteristic = nil;
}
3.發(fā)現(xiàn)服務
#pragma mark 發(fā)現(xiàn)服務
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
LogFunc
// 遍歷所有的服務
for (CBService *service in peripheral.services) {
if ([service.UUID.UUIDString isEqualToString:UUID0]) {
self.cbService = service;
[self.peripheral discoverCharacteristics:nil forService:service];
break;
}
}
}
- 發(fā)現(xiàn)特征值
#pragma mark 發(fā)現(xiàn)特征值
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
LogFunc
for (CBCharacteristic *characteristic in service.characteristics) {
// NSLog(@">>>服務:%@ 的 特征: %@,特征屬性properties:%d",service.UUID.UUIDString, characteristic.UUID.UUIDString, characteristic.properties);
if ([characteristic.UUID.UUIDString isEqualToString:WRITE_UUID]) {
self.writeCharacteristic = characteristic;
} else if ([characteristic.UUID.UUIDString isEqualToString:NOTIFY_UUID]) {
self.notifyCharacteristic = characteristic;
//訂閱,實時接收
if (self.notifyCharacteristic.properties & CBCharacteristicPropertyNotify) {
[self.peripheral setNotifyValue:YES forCharacteristic:self.notifyCharacteristic];
// [self.peripheral readValueForCharacteristic:self.notifyCharacteristic];
}
} else if ([characteristic.UUID.UUIDString isEqualToString:READ_UUID]) {
self.readCharacteristic = characteristic;
if (self.readCharacteristic.properties & CBCharacteristicPropertyRead) {
[self.peripheral readValueForCharacteristic:self.readCharacteristic];
}
}
}
// 測試寫數(shù)據(jù)
// 藍牙重置測試
NSString *resetString = @"00000302";
NSData *resetData = [Tool dataForHexString:resetString];
// NSData *testData = [[NSData alloc]initWithBytes:byteArray length:6];
// NSString *hexStr = [Tool ConvertToNSString:netData];
if (self.writeCharacteristic.properties & CBCharacteristicPropertyWrite) {
[self.peripheral writeValue:frequencyData forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithoutResponse];
}
}
發(fā)現(xiàn)描述descrptor 根據(jù)需求添加
接收藍牙數(shù)據(jù)
#pragma mark 接收藍牙數(shù)據(jù)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
LogFunc;
if (error) {
NSLog(@"error: %@", error);
return;
}
NSData *data = characteristic.value;
NSLog(@"uuid:%@ data:%@",characteristic.UUID.UUIDString, data);
}
- 藍牙寫入數(shù)據(jù)回調
#pragma mark 藍牙寫入數(shù)據(jù)回調 type為CBCharacteristicWriteWithResponse回調用
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
LogFunc;
if (error) {
}
}
通知回調
#pragma mark 設置通知回調
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
LogFunc
if (error) {
NSLog(@"error:%@", error);
return;
}
if (characteristic.isNotifying) {
[peripheral readValueForCharacteristic:characteristic];
}
}
8.項目中遇到的坑
- 寫給外設數(shù)據(jù)格式問題
- 跟硬件工程師溝通,需要傳輸?shù)臄?shù)據(jù)類型,一般都是傳byte字節(jié)數(shù)組,可能需要用到的方法
#pragma mark - MD5加密
-(NSString *)md5:(NSString *)inPutText
{
const char *cStr = [inPutText UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(cStr, strlen(cStr), result);
return [[NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
] lowercaseString];
}
#pragma mark - 將NSString轉換成十六進制的字符串則可使用如下方式:
- (NSString *)convertStringToHexStr:(NSString *)str {
if (!str || [str length] == 0) {
return @"";
}
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[data length]];
[data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
unsigned char *dataBytes = (unsigned char*)bytes;
for (NSInteger i = 0; i < byteRange.length; i++) {
NSString *hexStr = [NSString stringWithFormat:@"%x", (dataBytes[i]) & 0xff];
if ([hexStr length] == 2) {
[string appendString:hexStr];
} else {
[string appendFormat:@"0%@", hexStr];
}
}
}];
return string;
}
#pragma mark - 將十六進制的字符串轉換為需要的data梳毙,這就是你最終需要寫入的數(shù)據(jù):
+ (NSData *)dataWithHexString:(NSString *)hexstring
{
NSMutableData* data = [NSMutableData data];
int idx;
for (idx = 0; idx+2 <= hexstring.length; idx+=2) {
NSRange range = NSMakeRange(idx, 2);
NSString* hexStr = [hexstring substringWithRange:range];
NSScanner* scanner = [NSScanner scannerWithString:hexStr];
unsigned int intValue;
[scanner scanHexInt:&intValue];
[data appendBytes:&intValue length:1];
}
return data;
}
- 讀取數(shù)據(jù)格式問題
- 讀取的也是byte數(shù)組
#pragma mark - 更新特征的value的時候會調用 (凡是從藍牙傳過來的數(shù)據(jù)都要經過這個回調,簡單的說這個方法就是你拿數(shù)據(jù)的唯一方法) 你可以判斷是否
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
NSLog(@"%s, line = %d", __FUNCTION__, __LINE__);
NSLog(@"***********");
if (error) {
NSLog(@"error:%@",error.localizedDescription);
return;
}
NSData * data = characteristic.value;
Byte * resultByte = (Byte *)[data bytes];
NSLog(@"rusultByte:%s",resultByte);
for(int i=0;i<[data length];i++){
printf("testByteFF02[%d] = %d\n",i,resultByte[i]);
if (resultByte[i]==0) { // 授權成功
self.count++;
self.writeFinishFlag = YES;
break;
// [self.deviceManager writeValue:[@"Lock" dataUsingEncoding:NSUTF8StringEncoding] forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}else{
self.writeFinishFlag = NO;
}
}
if (self.count==1&& self.writeFinishFlag) { // // 授權成功
NSLog(@"授權成功");
if (self.lockCharacteristic.properties & CBCharacteristicPropertyWrite) {
// [self notifyCharacteristic:peripheral characteristic:characteristic];
// NSData *data = [NSData dataWithHexString:@"Lock"];
NSString *lock = [self convertStringToHexStr:@"Lock"];
NSLog(@"lock:%@",lock);
// NSData *data = [@"Lock" dataUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithHexString:lock];
NSLog(@"lockData:%@",data);
[self.deviceManager writeValue:data forCharacteristic:self.lockCharacteristic type:CBCharacteristicWriteWithResponse];// 激活鎖
if (characteristic.properties & CBCharacteristicPropertyRead) {
// [peripheral readValueForCharacteristic:characteristic];
}
}
}
if (self.count==2&& self.writeFinishFlag) {// 激活成功
NSLog(@"激活成功");
if (self.lockCharacteristic.properties & CBCharacteristicPropertyWrite) {
// [self notifyCharacteristic:peripheral characteristic:characteristic];
NSString *unLock = [self convertStringToHexStr:@"Unlock"];
NSData *data = [NSData dataWithHexString:unLock];
NSLog(@"unlockData;%@",data);
[self.deviceManager writeValue:data forCharacteristic:self.lockCharacteristic type:CBCharacteristicWriteWithResponse];// 開鎖
if (characteristic.properties & CBCharacteristicPropertyRead) {
// [peripheral readValueForCharacteristic:characteristic];
}
}
}
if (self.count==3&& self.writeFinishFlag) {// 開鎖成功
NSLog(@"開鎖成功");
[self.bleManager cancelPeripheralConnection:self.deviceManager];// 斷開鏈接
[self.bleManager stopScan];
self.bleManager = nil;
}
}
//base64解密
NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
NSString *decodedString = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding];
// 遇到的問題
藍牙調試助手測試notify捐下,要先設置notify為true才能監(jiān)聽數(shù)據(jù)讀寫
代碼也是账锹,要先拿到特征值并設置為true,才能去測試寫數(shù)據(jù)坷襟,不然代理方法監(jiān)聽不到數(shù)據(jù)讀入奸柬。
報錯:error: Error Domain=CBATTErrorDomain Code=2 "Reading is not permitted." UserInfo={NSLocalizedDescription=Reading is not permitted.}
設置notify特征值的時候不要去調入讀取數(shù)據(jù)的方法,[peripheral readValueForCharacteristic:characteristic];不然就會報錯婴程。設置notify不需要調入這個方法廓奕,只需要設置notify = true就可以了,這個方法是給讀取特征值去調入的档叔,notify不需要