本文默认读者对蓝牙开发有基础的了解, 与外设的交互使用 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