iOS 蓝牙BLE开发
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 蓝牙BLE开发相关的知识,希望对你有一定的参考价值。
参考技术A GAP(Generic Access Profile):它用来控制设备连接和广播,GAP 使你的设备被其他设备可见,并决定了你的设备是否可以或者怎样与合同设备进行交互。GATT(Generic Attribute Profile):BLE连接都是建立在GATT协议之上的。GATT 是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范,这些很短的数据段被称为属性(Attribute)。
BLE中主要有两个角色:外围设备(Peripheral)和中心设备(Central)。一个中心设备可以连接多个外围设备,一个外围设备包含一个或多个服务(services),一个服务包含一个或多个特征(characteristics)。
使用CoreBluetooth库,创建CBPeripheralManager,实现CBPeripheralManagerDelegate代理
创建完该对象,会回调peripheralManagerDidUpdateState:方法判断蓝牙状态,蓝牙可用,给外设配置服务和特征
注意CBAttributePermissions
当中心设备读写设置CBAttributePermissionsReadEncryptionRequired/CBAttributePermissionsWriteEncryptionRequired权限的Characteristic时,会弹出弹框,请求建立安全连接
给外设配置服务特征后,会调用peripheralManager:didAddService:error: 服务特征全部添加完后发起广播,如果在广播时设置CBAdvertisementDataServiceUUIDsKey,会把该service广播出去,中心设备在扫描时可根据该uuid找到该设备。外围设备靠不断发广播,使中心设备发现它。
当中央端连接上了此设备并订阅了特征时会回调 didSubscribeToCharacteristic:
当接收到中央端读的请求时会调用didReceiveReadRequest:
创建CBCentralManager对象,实现CBCentralManagerDelegate代理
回调centralManagerDidUpdateState:代理方法,当central.state==CBManagerStatePoweredOn时,开启扫描,设置serviceUUIDs可扫描特定外设,CBCentralManagerScanOptionAllowDuplicatesKey设为NO不重复扫描已发现设备,YES是允许
扫描到设备会回调centralManager:didDiscoverPeripheral:advertisementData:RSSI:,RSS绝对值越大,表示信号越差,设备离的越远
关闭扫描
连接设备
发现服务
发现特征
iOS BLE 模块开发总结
本文默认读者对蓝牙开发有基础的了解, 与外设的交互使用 BabyBluetooth.
一. 总结的要点如下:
1. iOS 蓝牙与外设连接的步骤.
2. 外设过滤, 服务, 特性.
3. 单模,双模蓝牙.
4. 外设的 UUID.
二. 实际应用场景:
通过 APP 控制荣泰按摩椅, 方便用户切换按摩模式.
三. 第一点对应 OC 代码
1. 在 BabyBluetooth 库中, BabyBluetooth Class 封装了与蓝牙外设交互的所有方法,通过舒适化 BabyBluetooth ,实现代理方法,就可以与外设进行通信.
//1 初始化
//2 扫描设备
//3 连接外设
//3.1 查找服务
//3.2 查找到Characteristics并筛选
//4 写入数据
//5 读取转换数据
2. 在搜索到蓝牙设备后,要知道每个蓝牙设备都会不少于一个的服务,每个服务都会有不少于一个的特性,我们与外设进行通信,是要约定好这个唯一的特性后,才可正确得进行串口通信.其中特性的属性有读,写两种,我们根据硬件工程师提供给我们的标识,可以获取到这两个需要的特性.
//连接Peripherals成功的委托
[weakSelf.baby setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral)
NSLog(@"已连接的设备:____%@", peripheral);
//搜索指定服务
[peripheral discoverServices: @[[CBUUID UUIDWithString:SERVICE_UUID]]];
self.isConnected = YES;
];
//查找服务
[weakSelf.baby setBlockOnDiscoverServices:^(CBPeripheral *peripheral, NSError *error)
for (CBService *service in peripheral.services)
if([service.UUID isEqual:[CBUUID UUIDWithString:SERVICE_UUID]])
[peripheral discoverCharacteristics:nil forService:service];
];
//查找到Characteristics的block
[weakSelf.baby setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error)
for (CBCharacteristic *c in service.characteristics)
if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_RX]])
NSLog(@"CHARACTERISTIC_RX:_____%@", c);
self.characteristicR_x = c;
if (c.isNotifying)
[self.baby cancelNotify:peripheral characteristic:c];
else
[self.peripheral setNotifyValue:YES forCharacteristic:c];
if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_TX]])
self.characteristicT_x = c;
NSLog(@"CHARACTERISTIC_TX:_____%@", c);
if (c.isNotifying)
[self.baby cancelNotify:peripheral characteristic:c];
else
[self.peripheral setNotifyValue:YES forCharacteristic:c];
];
4.有一些公司的外部设备的名字都是一样的,但是进行连接或者其他操作的时候,没必要知道具体的是哪一台设备,那么就可以使用蓝牙外设的UUIDString(即peripheral.identifier.UUIDString)来作为唯一标识。但是,需要注意的一点:不同的中心设备(也可以说是不同的手机)对于同一台蓝牙设备,获取到的UUIDString是不一样的。举例说明一下:对于同一台蓝牙设备,我的手机进行扫描,然后读取它的UUIDString,和你的手机进行扫描,获取到的UUIDString是不同的。
除了这些,在实际测试中,出现了这种情况:一些型号的按摩椅带有蓝牙音箱,因此,想要通过按摩椅播放手机中的歌曲,又要通过手机 APP 控制按摩椅需要进行两次蓝牙连接.首先在 APP 中发现蓝牙设备并连接,这里是对按摩椅进行控制;其次在设置中发现设备并连接,这里相当于连接了蓝牙音箱,连接后在设置中会出现两个蓝牙名称,如下图:
关于这一点,客户在进行测试的时候提出了这样一个问题:为什么需要两次蓝牙连接?而不是通过 APP 直接完成两次连接要达到的目的? 最佳合理的解释是: Apple 并没有赋予 CoreBluetooth 的接口最高的权限,所以通过 APP 进行的连接只可与蓝牙设备进行串口通信,而想要实现最高权限所能完成的功能,还是要依靠 iOS 系统的!
附: demo 主要代码
//
// ViewController.m
// BLE_DEMO
//
// Created by 张闯 on 17/5/10.
// Copyright © 2017年 张闯. All rights reserved.
//
#import "ViewController.h"
#import "DeviceViewController.h"
@interface ViewController () <UITableViewDelegate, UITableViewDataSource>
@property (nonatomic, strong) BabyBluetooth *baby;
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) NSMutableArray *dataSource;
@end
@implementation ViewController
- (void)viewWillAppear:(BOOL)animated
[super viewWillAppear:animated];
[self.baby cancelAllPeripheralsConnection];
- (void)viewDidLoad
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.title = @"设备列表";
self.tableView = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
[self.view addSubview:_tableView];
self.dataSource = [NSMutableArray array];
//初始化
self.baby = [BabyBluetooth shareBabyBluetooth];
//设置蓝牙代理方法
[self babyDelegate];
//扫描外设
self.baby.scanForPeripherals().begin();
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(refreshBLE)];
- (void)refreshBLE
[self.dataSource removeAllObjects];
[self.baby cancelAllPeripheralsConnection];
self.baby.scanForPeripherals().begin();
//设置蓝牙委托
- (void)babyDelegate
//设置扫描到设备的委托
__weak typeof(self) weakSelf = self;
[weakSelf.baby setBlockOnDiscoverToPeripherals:^(CBCentralManager *central, CBPeripheral *peripheral, NSDictionary *advertisementData, NSNumber *RSSI)
__strong typeof(self) strongSelf = self;
if (![strongSelf.dataSource containsObject:peripheral])
[strongSelf.dataSource addObject:peripheral];
[self.tableView reloadData];
];
//过滤器
//设置查找设备的过滤器
[weakSelf.baby setFilterOnDiscoverPeripherals:^BOOL(NSString *peripheralName, NSDictionary *advertisementData, NSNumber *RSSI)
if (peripheralName.length >1)
return YES;
return NO;
];
//设备状态改变
[weakSelf.baby setBlockOnCentralManagerDidUpdateState:^(CBCentralManager *central)
switch (central.state)
case CBManagerStatePoweredOn:
NSLog(@"CBManagerStatePoweredOn");
[SVProgressHUD showSuccessWithStatus:@"蓝牙已打开"];
break;
case CBManagerStatePoweredOff:
NSLog(@"CBManagerStatePoweredOff");
[SVProgressHUD showErrorWithStatus:@"蓝牙已关闭"];
break;
case CBManagerStateResetting:
NSLog(@"CBManagerStateResetting");
break;
case CBManagerStateUnsupported:
NSLog(@"CBManagerStateUnsupported");
break;
case CBManagerStateUnauthorized:
NSLog(@"CBManagerStateUnauthorized");
break;
case CBManagerStateUnknown:
NSLog(@"CBManagerStateUnknown");
break;
];
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
return _dataSource.count;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
cell.textLabel.text = [_dataSource[indexPath.row] name];
return cell;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
[tableView deselectRowAtIndexPath:indexPath animated:YES];
DeviceViewController *deviceVC = [[DeviceViewController alloc] init];
deviceVC.peripheral = _dataSource[indexPath.row];
[self.navigationController pushViewController:deviceVC animated:YES];
- (void)didReceiveMemoryWarning
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
@end
//
// DeviceViewController.m
// BLE_DEMO
//
// Created by 张闯 on 17/5/11.
// Copyright © 2017年 张闯. All rights reserved.
//
#import "DeviceViewController.h"
#define SERVICE_UUID @"XXXX"
#define CHARACTERISTIC_TX @"XXXX"
#define CHARACTERISTIC_RX @"XXXX"
@interface DeviceViewController ()
@property (nonatomic, strong) BabyBluetooth *baby;
@property (nonatomic, strong) CBCharacteristic *characteristicR_x;
@property (nonatomic, strong) CBCharacteristic *characteristicT_x;
@property (nonatomic, strong) NSArray *autoTypeArray;
@property (nonatomic, assign) BOOL isConnected;
@property (weak, nonatomic) IBOutlet UILabel *receivedDataLabel;
@end
@implementation DeviceViewController
- (NSArray *)autoTypeArray
if (!_autoTypeArray)
_autoTypeArray = @[@"Wake", @"Energize", @"Perform", @"Recover", @"Upper", @"Lower"];
return _autoTypeArray;
- (void)viewDidLoad
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.baby = [BabyBluetooth shareBabyBluetooth];
[self babyDelegate];
//连接外设
self.baby.having(self.peripheral).connectToPeripherals().begin();
self.baby.characteristicDetails(self.peripheral,self.characteristicR_x);
//设置蓝牙委托
- (void)babyDelegate
__weak typeof(self) weakSelf = self;
//连接Peripherals成功的委托
[weakSelf.baby setBlockOnConnected:^(CBCentralManager *central, CBPeripheral *peripheral)
NSLog(@"已连接的设备:____%@", peripheral);
//搜索指定服务
[peripheral discoverServices: @[[CBUUID UUIDWithString:SERVICE_UUID]]];
self.isConnected = YES;
];
//查找服务
[weakSelf.baby setBlockOnDiscoverServices:^(CBPeripheral *peripheral, NSError *error)
for (CBService *service in peripheral.services)
if([service.UUID isEqual:[CBUUID UUIDWithString:SERVICE_UUID]])
[peripheral discoverCharacteristics:nil forService:service];
];
//设置获取到最新Characteristics值的block
[weakSelf.baby setBlockOnReadValueForCharacteristic:^(CBPeripheral *peripheral, CBCharacteristic *characteristic, NSError *error)
char rData[999];
int len = (int)characteristic.value.length;
[characteristic.value getBytes:rData length:len];
NSData *d = [[NSData alloc] initWithBytes:rData length:len];
if (d.length == 19)
self.receivedDataLabel.text = [self transferData:d];
];
//查找到Characteristics的block
[weakSelf.baby setBlockOnDiscoverCharacteristics:^(CBPeripheral *peripheral, CBService *service, NSError *error)
for (CBCharacteristic *c in service.characteristics)
if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_RX]])
NSLog(@"CHARACTERISTIC_RX:_____%@", c);
self.characteristicR_x = c;
if (c.isNotifying)
[self.baby cancelNotify:peripheral characteristic:c];
else
[self.peripheral setNotifyValue:YES forCharacteristic:c];
if ([c.UUID isEqual:[CBUUID UUIDWithString:CHARACTERISTIC_TX]])
self.characteristicT_x = c;
NSLog(@"CHARACTERISTIC_TX:_____%@", c);
if (c.isNotifying)
[self.baby cancelNotify:peripheral characteristic:c];
else
[self.peripheral setNotifyValue:YES forCharacteristic:c];
];
//characteristic订阅状态改变的block
[weakSelf.baby setBlockOnDidUpdateNotificationStateForCharacteristic:^(CBCharacteristic *characteristic, NSError *error)
NSLog(@"uid:%@,isNotifying:%@",characteristic.UUID,characteristic.isNotifying?@"on":@"off");
];
//写Characteristic成功后的block
[weakSelf.baby setBlockOnDidWriteValueForCharacteristic:^(CBCharacteristic *characteristic, NSError *error)
NSLog(@"t_xCBCharacteristic: ____ %@", characteristic);
];
//断开Peripherals的连接
[weakSelf.baby setBlockOnDisconnect:^(CBCentralManager *central, CBPeripheral *peripheral, NSError *error)
NSLog(@"%@", peripheral);
if ([peripheral isEqual:self.peripheral])
[SVProgressHUD showErrorWithStatus:@"断开链接了"];
self.isConnected = NO;
];
- (IBAction)send:(id)sender
[self writeData:0x01];
- (IBAction)auto1:(id)sender
[self writeData:0x10];
- (IBAction)auto2:(id)sender
[self writeData:0x11];
- (IBAction)auto3:(id)sender
[self writeData:0x12];
- (IBAction)auto4:(id)sender
[self writeData:0x13];
- (IBAction)auto5:(id)sender
[self writeData:0x14];
- (IBAction)auto6:(id)sender
[self writeData:0x15];
- (void)writeData:(int)senderId
NSMutableData *data = [NSMutableData data];
uint8_t tmp[10];
uint8_t checksum = 0;
tmp[0] = 0xF0;
tmp[1] = 0x03;
tmp[2] = senderId;
checksum = tmp[1] + tmp[2];
checksum = ~checksum;
checksum = checksum & 0x7f;
tmp[3] = checksum;
tmp[4] = 0xF1;
[data appendBytes:(void *)(&tmp) length:5];
//写入数据
if (self.isConnected)
[self.peripheral writeValue:data forCharacteristic:self.characteristicT_x type:CBCharacteristicWriteWithResponse];
else
[SVProgressHUD showErrorWithStatus:@"连接已断开"];
- (NSString *)transferData:(NSData *)data
uint8_t *readTempBuffer = (uint8_t*)[data bytes];
//运行状态
int nChairRunState = (readTempBuffer[7]) & 0xf;
//手动判断
int nChairAutoType = (readTempBuffer[16] >> 2) & 0x0f;
if (nChairAutoType && nChairRunState == 3)
return self.autoTypeArray[nChairAutoType-1];
else
return @"none";
- (void)didReceiveMemoryWarning
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
*/
@end
以上是关于iOS 蓝牙BLE开发的主要内容,如果未能解决你的问题,请参考以下文章