本文主要以蓝牙4.0做介绍,因为现在iOS能用的蓝牙也就是只仅仅4.0的设备
用的库就是core bluetooth
这里是关于蓝牙的开发者官方文档,感兴趣的童鞋可以看一下点击进入 https://developer.apple.com/library/prerelease/content/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothOverview/CoreBluetoothOverview.html
其次就是了解一下蓝牙的一些概念吧.
core bluetooth就两个东西,peripheral和central 也就是外设和中心
如下图所示
图为Core Bluetooth 对象类模型
一般情况下蓝牙设备为外设,手机为中心,也就是在APP里面建立CBCentralManager获取到设备的peripheral这个类, 每个peripheral又会有若干个CBService,每个CBservice里面也会有若干个CBCharateristic,每个CBCharateristic里面又会有CBDescriptor.
看图
这句话说的真长.真费劲...!! 所以从网上盗了图.
蓝牙外设模型
了解的差不多了,下面就是手机作为中心连接外设的步骤了:
1.第一步当然是先建立中心管理者的角色了,成为代理,实现必须实现的代理方法也就是蓝牙更新状态的方法
2.确认蓝牙是打开的时候,我们就可以扫描周边设备了.扫描设备的时候要同时把设备保存的数组中,以便接下来连接的时候使用,
3.扫描到我们的设备之后就可以连接了.可以根据蓝牙的一些信息进行指令连接.例如:根据蓝牙名字连接等等.
4.连接上蓝牙之后就会生成一个peripheral对象,这时候我们就可以拿着这个对象进行操作了,例如查看外设信息,向这个外设读写信息等等.嗯,回归正题,拿到这个peripheral对象之后
获取peripheral的services,在获取services里的characteristics,拿到可以读写的characteristic并把这个characteristic记录下来
5.现在我们就可以进行数据交互了.可以向characteristic里面写数据 以及从characteristic读数据.嗯,读数据也就是订阅通知
6.完成后就可以断开连接了.
手机作为中心连接外设的步骤也就到这里结束了.下面就开始贴代码了.
我觉得这个代码贴的不太好,留下github地址,下载可运行.这样方便多了.
https://github.com/907064772/iOS----BluetoothCentral
---------****--------开始贴代码
import "ViewController.h"
import <CoreBluetooth/CoreBluetooth.h>
import "NSString+Emoji.h"
pragma step1 导入coreBluetooth头文件,建立主设备管理类,设置主设备委托
@interface ViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate,UITableViewDelegate,UITableViewDataSource>{
CBCentralManager * manager;
CBPeripheral *per;
// 记录下写的通道
CBCharacteristic * WriteCharacteristic;
// 记录下读的通道
CBCharacteristic * ReadCharacteristic;
}
//扫描到的蓝牙外设
@property (nonatomic,strong) NSMutableArray *discoverPeripherals;
@property (nonatomic,strong) UITableView *tableview;
//通知显示label
@property (weak, nonatomic) IBOutlet UILabel *notifyLabel;
//发送按钮的方法
- (IBAction)notifyBtnClick:(UIButton *)sender;
@property (weak, nonatomic) IBOutlet UITextView *textView;
@property (weak, nonatomic) IBOutlet UIButton *sendBtn;
/**
- 点击选择是否以十六进制发送显示
*/
- (IBAction)isHexBtnClick:(UIButton *)sender;
/**
- 是否以十六进制输入输出标记
/
@property (nonatomic,assign) BOOL isHex;
/* - 发送方法
*/
- (IBAction)sendAction:(UIButton )sender;
/*
- 从蓝牙串口监听到的所有消息字符串
*/
@property (nonatomic,copy) NSMutableString *notifyString;
/**
- 写入的字符串
*/
@property (nonatomic,copy) NSMutableString *writeString;
/**
- 监听蓝牙串口广播按钮
*/
@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];
// 初始化一个蓝牙中心管理者并设置代理和线程,默认线程为住线程
manager = [[CBCentralManager alloc]initWithDelegate:self queue:dispatch_get_main_queue()];
// [self createTableView];
}
/**
- 创建表格视图
*/
-(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];
// 连接设备 如果蓝牙串口名字前缀有 这个就连接
if ([peripheral.name hasPrefix:@"SerialCom"]) {
/
* 一个设备最多能连7个外设,每个外设最多只能给一个住设备连接,连接成功,失败,断开会进入各自的委托.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托
*/
//找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!
[manager connectPeripheral:peripheral options:nil];
}
}
pragma step2 扫描外设(discover),扫描外设的方法我们放在centrallManager成功打开的委托中,因为只有设备成功打开,才能开始扫描,否则会报错
/**
蓝牙中心管理者必须实现的代理方法.更新状态
-
@param central
*/
-(void)centralManagerDidUpdateState:(CBCentralManager *)central{/*!
- @enum CBCentralManagerState
- @discussion Represents the current state of a CBCentralManager.
- @constant CBCentralManagerStateUnknown 未知状态,更新迫在眉睫
- @constant CBCentralManagerStateResetting 与系统连接服务暂时丢失,
- @constant CBCentralManagerStateUnsupported 这个平台不支持蓝牙 中心/客户端 角色
- @constant CBCentralManagerStateUnauthorized 此应用未被授权
- @constant CBCentralManagerStatePoweredOff 蓝牙当前状态为关闭
- @constant CBCentralManagerStatePoweredOn 蓝牙当前状态为打开
/
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");
/*
* 第一个参数nil就是扫描周围所有的外设,扫描到外设后会进入
*/
// [manager scanForPeripheralsWithServices:nil options:nil];
[manager scanForPeripheralsWithServices:@[] options:nil];
break;
default:
break;
}
}
pragma step3 连接外设(connect)
/**
扫描到设备会进入方法
@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(@"当前扫描到设备:%@",peripheral);
[_discoverPeripherals addObject:peripheral];
// 连接设备
if ([peripheral.name hasPrefix:@"SerialCom"]) {
/**
* 一个设备最多能连7个外设,每个外设最多只能给一个住设备连接,连接成功,失败,断开会进入各自的委托.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral;//连接外设成功的委托
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//外设连接失败的委托
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error;//断开外设的委托
*/
//找到的设备必须持有它,否则CBCentralManager中也不会保存peripheral,那么CBPeripheralDelegate中的方法也不会被调用!!
[self.tableview reloadData];
[manager connectPeripheral:peripheral options:nil];
}
}
pragma step4 扫描外设中的服务和特征(discover)
/**
- 设备连接成功后,就可以扫描设备的服务了.同样是通过委托形式,扫描到结果后进入委托方法,但是这个委托已经不再是主设备的委托(CBCentralManagerDelegate),而是外设的委托(CBPeripheralDelegate),这个委托包含了主设备与外设交互的许多回调方法,包括获取services,获取characteristics ,获取characteristics的值,获取characteristics的descirptor和descriptor的只,写数据,读rssi,用通知的方式订阅数据等等.
*/
pragma mark - centralManagerDelegate
/**
- 连接外设成功委托
- @param central <#central description#>
- @param peripheral <#peripheral description#>
*/
-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
NSLog(@"连接到名称为(%@)的设备-成功",peripheral.name);
// 设置peripheral的委托
peripheral.delegate = self;
// 记录下当前连接上的外设
per = peripheral;
// 扫描外设servers,成功后会进入 - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(nullable NSError *)error;
[peripheral discoverServices:nil];
// 连接成功后停止扫描
[central stopScan];
}
/**
- 连接外设失败的委托
- @param central
- @param peripheral
- @param error
*/
-(void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral )peripheral error:(NSError )error{
NSLog(@"连接到名称为(%@)的设备-失败,原因:%@",peripheral.name,[error localizedDescription]);
}
/ - Peripheral断开连接
- @param central
- @param peripheral
- @param error
*/
-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"外设连接断开连接设备:%@,原因:%@",peripheral.name,[error localizedDescription]);
}
pragma mark - peripheralDelegate
/**
- 扫描到services,获取外设的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);
// 扫描每个service的characteristics,扫描到会进入 -(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error 代理方法中
[peripheral discoverCharacteristics:nil forService:service];
}
}
/**
获取外设的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的值.读到数据会进入 -peripheral:didUpdateValueForCharacteristic:error:方法
for (CBCharacteristic *characteristic in service.characteristics) {
[peripheral readValueForCharacteristic:characteristic];
}// 获取characteristics的descriptor值,读取到数据会进入到
// - 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);
// 记录下当前通道.在我的蓝牙设备里面,FFF6的权限是可读写的就是RW,但是实际应用中应该判断 characteristic.properties然后在进行保存
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
// 这个descriptor都是对于charateristic的描述,一般都是字符串.所以这里我们用了字符串拼接
NSLog(@"characteristic.uuid:%@ value:%@",[NSString stringWithFormat:@"%@",descriptor.UUID],descriptor.value);
}
//波特率115200
pragma step5 把数据写到characteristic中
/**
-
写数据
*/
-(void)writeCharacteristic:(CBPeripheral *)peripheral characteristic:(CBCharacteristic *)characteristic value:(NSData )value{
//打印出 characteristic 的权限,可以看到有很多种,这是一个NS_OPTIONS,就是可以同时用于好几个值,常见的有read,write,notify,indicate,知知道这几个基本就够用了,前连个是读写权限,后两个都是通知,两种不同的通知方式。
/
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 的权限才可以写
if (characteristic.properties & CBCharacteristicPropertyWrite) {
// 最好一个type参数可以为CBCharacteristicWriteWithResponse或者type:CBCharacteristicWrite,区别是是否会有反馈
[peripheral writeValue:value forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}else{
NSLog(@"改字段不可写");
}
}
/** 设置通知
*/
-(void)notifyCharacteristic:(CBPeripheral )peripheral characteristic:(CBCharacteristic )characteristic{
// 设置通知.数据通知会进入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;
// 判断十六进制是否打开,如果要发送十六进制数据就转换字符串
if (self.isHex) {
tmpString = [NSString hexStringFromString:tmpString];
}
// 想制定特征写入字符
[self writeCharacteristic:per characteristic:WriteCharacteristic value:[tmpString dataUsingEncoding:NSUTF8StringEncoding]];
}
//是否开启蓝牙串口读取字符串(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];
}
//点击切换编码
- (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:@""];
}