本文主要以藍(lán)牙4.0做介紹,因?yàn)楝F(xiàn)在iOS能用的藍(lán)牙也就是只僅僅4.0的設(shè)備
用的庫就是core bluetooth
這里是關(guān)于藍(lán)牙的開發(fā)者官方文檔,感興趣的童鞋可以看一下點(diǎn)擊進(jìn)入 https://developer.apple.com/library/prerelease/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothOverview/CoreBluetoothOverview.html
其次就是了解一下藍(lán)牙的一些概念吧.
core bluetooth就兩個(gè)東西,peripheral和central 也就是外設(shè)和中心
如下圖所示

圖為Core Bluetooth 對(duì)象類模型
一般情況下藍(lán)牙設(shè)備為外設(shè),手機(jī)為中心,也就是在APP里面建立CBCentralManager獲取到設(shè)備的peripheral這個(gè)類, 每個(gè)peripheral又會(huì)有若干個(gè)CBService,每個(gè)CBservice里面也會(huì)有若干個(gè)CBCharateristic,每個(gè)CBCharateristic里面又會(huì)有CBDescriptor.
看圖

這句話說的真長(zhǎng).真費(fèi)勁...!! 所以從網(wǎng)上盜了圖.
藍(lán)牙外設(shè)模型
了解的差不多了,下面就是手機(jī)作為中心連接外設(shè)的步驟了:
1.第一步當(dāng)然是先建立中心管理者的角色了,成為代理,實(shí)現(xiàn)必須實(shí)現(xiàn)的代理方法也就是藍(lán)牙更新狀態(tài)的方法
2.確認(rèn)藍(lán)牙是打開的時(shí)候,我們就可以掃描周邊設(shè)備了.掃描設(shè)備的時(shí)候要同時(shí)把設(shè)備保存的數(shù)組中,以便接下來連接的時(shí)候使用,
3.掃描到我們的設(shè)備之后就可以連接了.可以根據(jù)藍(lán)牙的一些信息進(jìn)行指令連接.例如:根據(jù)藍(lán)牙名字連接等等.
4.連接上藍(lán)牙之后就會(huì)生成一個(gè)peripheral對(duì)象,這時(shí)候我們就可以拿著這個(gè)對(duì)象進(jìn)行操作了,例如查看外設(shè)信息,向這個(gè)外設(shè)讀寫信息等等.嗯,回歸正題,拿到這個(gè)peripheral對(duì)象之后
獲取peripheral的services,在獲取services里的characteristics,拿到可以讀寫的characteristic并把這個(gè)characteristic記錄下來
5.現(xiàn)在我們就可以進(jìn)行數(shù)據(jù)交互了.可以向characteristic里面寫數(shù)據(jù) 以及從characteristic讀數(shù)據(jù).嗯,讀數(shù)據(jù)也就是訂閱通知
6.完成后就可以斷開連接了.
手機(jī)作為中心連接外設(shè)的步驟也就到這里結(jié)束了.下面就開始貼代碼了.
我覺得這個(gè)代碼貼的不太好,留下github地址,下載可運(yùn)行.這樣方便多了.
https://github.com/907064772/iOS----BluetoothCentral
---------****--------開始貼代碼
import "ViewController.h"
import <CoreBluetooth/CoreBluetooth.h>
import "NSString+Emoji.h"
pragma step1 導(dǎo)入coreBluetooth頭文件,建立主設(shè)備管理類,設(shè)置主設(shè)備委托
@interface ViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate,UITableViewDelegate,UITableViewDataSource>{
CBCentralManager * manager;
CBPeripheral *per;
// 記錄下寫的通道
CBCharacteristic * WriteCharacteristic;
// 記錄下讀的通道
CBCharacteristic * ReadCharacteristic;
}
//掃描到的藍(lán)牙外設(shè)
@property (nonatomic,strong) NSMutableArray *discoverPeripherals;
@property (nonatomic,strong) UITableView *tableview;
//通知顯示label
@property (weak, nonatomic) IBOutlet UILabel *notifyLabel;
//發(fā)送按鈕的方法
- (IBAction)notifyBtnClick:(UIButton *)sender;
@property (weak, nonatomic) IBOutlet UITextView *textView;
@property (weak, nonatomic) IBOutlet UIButton *sendBtn;
/**
- 點(diǎn)擊選擇是否以十六進(jìn)制發(fā)送顯示
*/
- (IBAction)isHexBtnClick:(UIButton *)sender;
/**
- 是否以十六進(jìn)制輸入輸出標(biāo)記
/
@property (nonatomic,assign) BOOL isHex;
/* - 發(fā)送方法
*/
- (IBAction)sendAction:(UIButton )sender;
/*
- 從藍(lán)牙串口監(jiān)聽到的所有消息字符串
*/
@property (nonatomic,copy) NSMutableString *notifyString;
/**
- 寫入的字符串
*/
@property (nonatomic,copy) NSMutableString *writeString;
/**
- 監(jiān)聽藍(lán)牙串口廣播按鈕
*/
@property (weak, nonatomic) IBOutlet UIButton *listenBtn;
@property (weak, nonatomic) IBOutlet UIButton *cleanBtnClick;
- (IBAction)cleanBtnClick:(id)sender;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_discoverPeripherals = [[NSMutableArray alloc]init];
// 初始化一個(gè)藍(lán)牙中心管理者并設(shè)置代理和線程,默認(rèn)線程為住線程
manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
// [self createTableView];
}
/**
- 創(chuàng)建表格視圖
*/
-(void)createTableView{
CGRect frame = self.view.frame;
frame.origin.y += 20;
self.tableview = [[UITableView alloc]initWithFrame:frame style:UITableViewStylePlain];
self.tableview.delegate = self;
self.tableview.dataSource = self;
[self.view addSubview:self.tableview];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.discoverPeripherals.count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
CBPeripheral * peripheral = self.discoverPeripherals[indexPath.row];
cell.textLabel.text = peripheral.name;
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath )indexPath{
CBPeripheral peripheral = self.discoverPeripherals[indexPath.row];
// 連接設(shè)備 如果藍(lán)牙串口名字前綴有 這個(gè)就連接
if ([peripheral.name hasPrefix:@"SerialCom"]) {
/
* 一個(gè)設(shè)備最多能連7個(gè)外設(shè),每個(gè)外設(shè)最多只能給一個(gè)住設(shè)備連接,連接成功,失敗,斷開會(huì)進(jìn)入各自的委托.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設(shè)成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設(shè)連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設(shè)的委托
*/
//找到的設(shè)備必須持有它,否則CBCentralManager中也不會(huì)保存peripheral,那么CBPeripheralDelegate中的方法也不會(huì)被調(diào)用!!
[manager connectPeripheral:peripheral options:nil];
}
}
pragma step2 掃描外設(shè)(discover),掃描外設(shè)的方法我們放在centrallManager成功打開的委托中,因?yàn)橹挥性O(shè)備成功打開,才能開始掃描,否則會(huì)報(bào)錯(cuò)
/**
藍(lán)牙中心管理者必須實(shí)現(xiàn)的代理方法.更新狀態(tài)
-
@param central
*/
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{/*!
- @enum CBCentralManagerState
- @discussion Represents the current state of a CBCentralManager.
- @constant CBCentralManagerStateUnknown 未知狀態(tài),更新迫在眉睫
- @constant CBCentralManagerStateResetting 與系統(tǒng)連接服務(wù)暫時(shí)丟失,
- @constant CBCentralManagerStateUnsupported 這個(gè)平臺(tái)不支持藍(lán)牙 中心/客戶端 角色
- @constant CBCentralManagerStateUnauthorized 此應(yīng)用未被授權(quán)
- @constant CBCentralManagerStatePoweredOff 藍(lán)牙當(dāng)前狀態(tài)為關(guān)閉
- @constant CBCentralManagerStatePoweredOn 藍(lán)牙當(dāng)前狀態(tài)為打開
/
switch (central.state) {
case CBCentralManagerStateUnknown:
NSLog(@"CBCentralManagerStateUnknown");
break;
case CBCentralManagerStateResetting:
NSLog(@"CBCentralManagerStateResetting");
break;
case CBCentralManagerStateUnsupported:
NSLog(@"CBCentralManagerStateUnsupported");
break;
case CBCentralManagerStateUnauthorized:
NSLog(@"CBCentralManagerStateUnauthorized");
break;
case CBCentralManagerStatePoweredOff:
NSLog(@"CBCentralManagerStatePoweredOff");
break;
case CBCentralManagerStatePoweredOn:
NSLog(@"CBCentralMnagerStatePoweredOn");
/*
* 第一個(gè)參數(shù)nil就是掃描周圍所有的外設(shè),掃描到外設(shè)后會(huì)進(jìn)入
*/
// [manager scanForPeripheralsWithServices:nil options:nil];
[manager scanForPeripheralsWithServices:@[] options:nil];
break;
default:
break;
}
}
pragma step3 連接外設(shè)(connect)
/**
掃描到設(shè)備會(huì)進(jìn)入方法
@param central <#central description#>
@param peripheral <#peripheral description#>
@param advertisementData <#advertisementData description#>
-
@param RSSI <#RSSI description#>
*/
-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{NSLog(@"當(dāng)前掃描到設(shè)備:%@",peripheral);
[_discoverPeripherals addObject:peripheral];
// 連接設(shè)備
if ([peripheral.name hasPrefix:@"SerialCom"]) {
/**
* 一個(gè)設(shè)備最多能連7個(gè)外設(shè),每個(gè)外設(shè)最多只能給一個(gè)住設(shè)備連接,連接成功,失敗,斷開會(huì)進(jìn)入各自的委托.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//連接外設(shè)成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外設(shè)連接失敗的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//斷開外設(shè)的委托
*/
//找到的設(shè)備必須持有它,否則CBCentralManager中也不會(huì)保存peripheral,那么CBPeripheralDelegate中的方法也不會(huì)被調(diào)用!!
[self.tableview reloadData];
[manager connectPeripheral:peripheral options:nil];
}
}
pragma step4 掃描外設(shè)中的服務(wù)和特征(discover)
/**
- 設(shè)備連接成功后,就可以掃描設(shè)備的服務(wù)了.同樣是通過委托形式,掃描到結(jié)果后進(jìn)入委托方法,但是這個(gè)委托已經(jīng)不再是主設(shè)備的委托(CBCentralManagerDelegate),而是外設(shè)的委托(CBPeripheralDelegate),這個(gè)委托包含了主設(shè)備與外設(shè)交互的許多回調(diào)方法,包括獲取services,獲取characteristics ,獲取characteristics的值,獲取characteristics的descirptor和descriptor的只,寫數(shù)據(jù),讀rssi,用通知的方式訂閱數(shù)據(jù)等等.
*/
pragma mark - centralManagerDelegate
/**
- 連接外設(shè)成功委托
- @param central <#central description#>
- @param peripheral <#peripheral description#>
*/
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
NSLog(@"連接到名稱為(%@)的設(shè)備-成功",peripheral.name);
// 設(shè)置peripheral的委托
peripheral.delegate = self;
// 記錄下當(dāng)前連接上的外設(shè)
per = peripheral;
// 掃描外設(shè)servers,成功后會(huì)進(jìn)入 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
[peripheral discoverServices:nil];
// 連接成功后停止掃描
[central stopScan];
}
/**
- 連接外設(shè)失敗的委托
- @param central
- @param peripheral
- @param error
*/
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral )peripheral error:(NSError )error{
NSLog(@"連接到名稱為(%@)的設(shè)備-失敗,原因:%@",peripheral.name,[error localizedDescription]);
}
/ - Peripheral斷開連接
- @param central
- @param peripheral
- @param error
*/
-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"外設(shè)連接斷開連接設(shè)備:%@,原因:%@",peripheral.name,[error localizedDescription]);
}
pragma mark - peripheralDelegate
/**
- 掃描到services,獲取外設(shè)的services
- @param peripheral
- @param error
*/
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
if (error) {
NSLog(@">>>discovered services for %@ with error: %@",peripheral.name,[error localizedDescription]);
return;
}
for (CBService *service in peripheral.services) {
NSLog(@"service.UUID:%@",service.UUID);
// 掃描每個(gè)service的characteristics,掃描到會(huì)進(jìn)入 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error 代理方法中
[peripheral discoverCharacteristics:nil forService:service];
}
}
/**
獲取外設(shè)的characteristics,獲取characteristics的值,獲取characteristics的descriptor和descriptor的值
@param peripheral
@param service
-
@param error
*/
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
if (error) {
NSLog(@"error discovered characteristics for %@ with error: %@",service.UUID,[error localizedDescription]);
return;
}
// 獲取characteristics的值.讀到數(shù)據(jù)會(huì)進(jìn)入 -peripheral:didUpdateValueForCharacteristic:error:方法
for (CBCharacteristic *characteristic in service.characteristics) {
[peripheral readValueForCharacteristic:characteristic];
}// 獲取characteristics的descriptor值,讀取到數(shù)據(jù)會(huì)進(jìn)入到
// - peripheral:didDiscoverDescriptorsForCharacteristic:error:for (CBCharacteristic *charateristic in service.characteristics) {
[peripheral discoverDescriptorsForCharacteristic:charateristic];
}
}
/**
- 獲取characteristic的值
- @param peripheral
- @param characteristic
- @param error
*/
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError )error{
NSString hexString =[[NSString alloc]initWithData:characteristic.value encoding:NSUTF8StringEncoding];
if (self.isHex) {
hexString = [NSString hexStringFromString:hexString];
}
[_notifyString appendFormat:@"%@ ",hexString];
// 打印charateristic的uuid值和value值
_notifyLabel.text = [NSString stringWithFormat:@"%@",_notifyString];
NSLog(@"characteristic.uuid:%@ value:%@",characteristic.UUID,characteristic.value);
}
/ - 搜索到characteristic的descriptors
- @param peripheral
- @param descriptor
- @param error
*/
-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError )error{
NSLog(@"characteristic:%@ properties:%ld",characteristic.UUID,characteristic.properties);
// 記錄下當(dāng)前通道.在我的藍(lán)牙設(shè)備里面,FFF6的權(quán)限是可讀寫的就是RW,但是實(shí)際應(yīng)用中應(yīng)該判斷 characteristic.properties然后在進(jìn)行保存
if ([[characteristic.UUID UUIDString] isEqual:@"FFF6"]) {
WriteCharacteristic = characteristic;
}
// 讀通道
if ([[characteristic.UUID UUIDString] isEqual:@"FFF7"]) {
ReadCharacteristic = characteristic;
}
for (CBDescriptor descriptor in characteristic.descriptors) {
NSLog(@"descriptor.uuid:%@",descriptor.UUID);
}
_listenBtn.enabled = YES;
}
/ - 獲取到descriptor的值
- @param peripheral
- @param descriptor
- @param error
*/
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error{
// 打印出descriptorUUID和value
// 這個(gè)descriptor都是對(duì)于charateristic的描述,一般都是字符串.所以這里我們用了字符串拼接
NSLog(@"characteristic.uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
//波特率115200
pragma step5 把數(shù)據(jù)寫到characteristic中
/**
-
寫數(shù)據(jù)
*/
-(void)writeCharacteristic:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic value:(NSData )value{
//打印出 characteristic 的權(quán)限,可以看到有很多種,這是一個(gè)NS_OPTIONS,就是可以同時(shí)用于好幾個(gè)值,常見的有read,write,notify,indicate,知知道這幾個(gè)基本就夠用了,前連個(gè)是讀寫權(quán)限,后兩個(gè)都是通知,兩種不同的通知方式。
/
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
CBCharacteristicPropertyBroadcast = 0x01,
CBCharacteristicPropertyRead = 0x02,
CBCharacteristicPropertyWriteWithoutResponse = 0x04,
CBCharacteristicPropertyWrite = 0x08,
CBCharacteristicPropertyNotify = 0x10,
CBCharacteristicPropertyIndicate = 0x20,
CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,
CBCharacteristicPropertyExtendedProperties = 0x80,
CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x100,
CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(NA, 6_0) = 0x200
};*/
NSLog(@"%lu",(unsigned long)characteristic.properties);
// 只有characteristic.properties 有write 的權(quán)限才可以寫
if (characteristic.properties & CBCharacteristicPropertyWrite) {
// 最好一個(gè)type參數(shù)可以為CBCharacteristicWriteWithResponse或者type:CBCharacteristicWrite,區(qū)別是是否會(huì)有反饋
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}else{
NSLog(@"改字段不可寫");
}
}
/** 設(shè)置通知
*/
-(void)notifyCharacteristic:(CBPeripheral )peripheral characteristic:(CBCharacteristic )characteristic{
// 設(shè)置通知.數(shù)據(jù)通知會(huì)進(jìn)入didUpdateValueForCharacteristic方法
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
/取消通知
*/
-(void)cancelNotifuCharacteristic:(CBPeripheral )perpheral characteristic:(CBCharacteristic )characteristic{
// 取消通知
[perpheral setNotifyValue:NO forCharacteristic:characteristic];
}
/停止掃描并斷開連接
*/
-(void)disconnectPeripheral:(CBCentralManager *)centralManager peripheral:(CBPeripheral *)periphearl{
// 停止掃描
[centralManager stopScan];
// 斷開連接
[centralManager cancelPeripheralConnection:periphearl];
}
-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"peripheral:%@ characteristic:%@ error:%@",peripheral,characteristic,[error localizedDescription]);
}
(void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}(IBAction)sendAction:(UIButton *)sender {
NSString *tmpString = _textView.text;
// 判斷十六進(jìn)制是否打開,如果要發(fā)送十六進(jìn)制數(shù)據(jù)就轉(zhuǎn)換字符串
if (self.isHex) {
tmpString = [NSString hexStringFromString:tmpString];
}
// 想制定特征寫入字符
[self writeCharacteristic:per characteristic:WriteCharacteristic value:[tmpString dataUsingEncoding:NSUTF8StringEncoding]];
}
//是否開啟藍(lán)牙串口讀取字符串(IBAction)notifyBtnClick:(UIButton *)sender {
sender.selected = !sender.isSelected;
if (sender.selected) {
_notifyString = [[NSMutableString alloc]init];
[self notifyCharacteristic:per characteristic:ReadCharacteristic];
}else{
_notifyString = nil;
[self cancelNotifuCharacteristic:per characteristic:ReadCharacteristic];
}
}
//取消可編輯
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.view endEditing:NO];
}
//點(diǎn)擊切換編碼
- (IBAction)isHexBtnClick:(UIButton *)sender {
sender.selected = !sender.selected;
if (sender.selected) {
_isHex = NO;
[sender setTitle:@"UTF-8" forState:UIControlStateSelected];
}else{
_isHex = YES;
[sender setTitle:@"Hex" forState:UIControlStateNormal];
}
}
//清空消息
- (IBAction)cleanBtnClick:(id)sender {
_notifyLabel.text = [NSString stringWithFormat:@""];
}