今天心血来潮,想学一下蓝牙开发,然后搜了一下,找了相关的一些文章看了看,大概有了一些了解,在这里做一个记录.
蓝牙模式分为两类,一种是中心模式,一种是外设模式;
基本概念:
- BLE:(蓝牙低能量)蓝牙4.0设备因为低耗电,也叫BLE
- 周边,中央:外设和中心设备,发起链接的是中央(一般是指手机),被链接的设备是外围设备(运动手环)
- service and characteristic:(服务和特征)每个设备会提供服务和特征,类似于服务端的API,但是结构不同。每个设备会有很多服务,每个服务中包含很多字段,这些字段的权限一般分为读(read),写(write),通知(notify)几种,就是我们连接设备后具体需要操作的内容
- Description:每个characteristic可以对应一个或者多个Description用于描述characteristic的信息或属性(eg.范围,计量单位)
蓝牙的基本流程
BLE中心模式流程
- 建立中心角色
- 扫描外设(Discover Peripheral)
- 连接外设(Connect Peripheral)
- 扫描外设中的服务和特征(Discover Services And Characteristics)
4.1 获取外设的services
4.2 获取外设的Characteristics,获取characteristics的值,,获取Characteristics的Descriptor和Descriptor的值 - 利用特征与外设做数据交互(Explore And Interact)
- 订阅Characteristic的通知
- 断开连接(Disconnect)
BLE外设模式流程
- 启动一个Peripheral管理对象
- 本地peripheral设置服务,特征,描述,权限等等
- peripheral发送广告
- 设置处理订阅,取消订阅,读characteristic,写characteristic的代理方法
蓝牙设备的状态
- 待机状态(standby):设备没有传输和发送数据,并且没有连接到任何外设
- 广播状态(Advertiser):周期性广播状态
- 扫描状态(Scanner):主动搜索正在广播的设备
- 发起链接状态(Initiator):主动向扫描设备发起连接
- 主设备(Master):作为主设备连接到其它设备.
- 从设备(Slave):作为从设备链接到其它设备
蓝牙设备的五种工作状态
- 准备(Standby)
- 广播(Advertising)
- 监听扫描(Scanning)
- 发起连接(Initiating)
- 已连接(Connected)
蓝牙和版本使用限制
- 蓝牙2.0:越狱设备
- BLE:iOS6以上
- MFI认证设备:无限制
BLE测试
- 两台BLE设备
- 如何让iOS模拟器也能测试BLE?
- 买一个CSR蓝牙4.0 USB适配器,插在Mac上
- 在终端输入sudo nvram bluetoothHostControllerSwitchBehavior="never"
- 重启Mac
- 用Xcode4.6调试代码,将程序跑在iOS6.1模拟器上
- 苹果把iOS7.0模拟器对BLE的支持移除了
(以上内容来自博客CoreBluetooth:baseK,中心模式,外设模式流程,iBeacon)
在iOS中,现在提供了一个CoreBlueTooth库,来操作蓝牙。相应代码如下
@interface ViewController ()<CBCentralManagerDelegate, CBPeripheralDelegate>
@property (strong, nonatomic) UIButton *scanButton;
@property (strong, nonatomic) CBCentralManager *cbCentralManager;
@property (strong, nonatomic) CBPeripheral *peripheral;
@end
@implementation ViewController
#pragma mark ==================== 懒加载 ====================
- (UIButton *)scanButton
{
if (!_scanButton)
{
_scanButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_scanButton addTarget:self action:@selector(scanButtonClickAction:) forControlEvents:UIControlEventTouchUpInside];
[_scanButton setTitle:@"立即扫描" forState:UIControlStateNormal];
[_scanButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
}
return _scanButton;
}
#pragma mark ==================== viewDidLoad ====================
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupUI];
}
- (void)setupUI
{
[self.view addSubview:self.scanButton];
[self.scanButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).offset(20);
make.centerX.equalTo(self.view.mas_centerX);
make.height.mas_equalTo(40);
make.width.mas_equalTo(200);
}];
}
#pragma mark ==================== 点击事件 ====================
- (void)scanButtonClickAction:(UIButton *)button
{
_cbCentralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
}
#pragma mark ==================== CBCentralManagerDelegate ====================
//当蓝牙状态发生变化时调用
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
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:
{
UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"前往打开蓝牙设置" message:nil preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil];
[alertC addAction:confirmAction];
[self presentViewController:alertC animated:YES completion:nil];
break;
}
case CBCentralManagerStatePoweredOn:
{
//开始扫描
// [self.cbCentralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
[self.cbCentralManager scanForPeripheralsWithServices:nil options:nil];
break;
}
default:
break;
}
}
/**
* 扫描到蓝牙设备后调用
* central 中心
* peripheral 外设
* advertisementData 外设广播的数据
* RSSI 接受信号强度,根据信号强度可以计算蓝牙设备的距离,计算公式为( d = 10^((abs(RSSI) - A) / (10 * n)) ),其中:d - 计算所得距离, RSSI - 接收信号强度(负值), A - 发射端和接收端相隔1米时的信号强度, n - 环境衰减因子
*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI
{
//拿到小米手环的外设
if ([peripheral.name containsString:@"MI Band 2"])
{
_peripheral = peripheral;
//停止扫描
[self.cbCentralManager stopScan];
//开始连接
[self.cbCentralManager connectPeripheral:_peripheral options:nil];
}
}
//当连接外设成功时调用
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
NSLog(@"连接外设成功");
//设置代理
[_peripheral setDelegate:self];
//扫描外设中的服务
[peripheral discoverServices:nil];
}
//当连接外设失败时调用
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"连接外设失败--%@", error);
}
//当外设时区连接时调用,可以在在这里实现自动重新连接
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
NSLog(@"丢失连接");
//重新连接
[_cbCentralManager connectPeripheral:_peripheral options:nil];
}
#pragma mark ==================== CBPeripheralDelegate ====================
/**
* 扫描到的小米手环的服务
* "<CBService: 0x1c06682c0, isPrimary = YES, UUID = Device Information>",
* "<CBService: 0x1c06684c0, isPrimary = YES, UUID = 00001530-0000-3512-2118-0009AF100700>",
* "<CBService: 0x1c0668500, isPrimary = YES, UUID = 1811>",
* "<CBService: 0x1c0668540, isPrimary = YES, UUID = 1802>",
* "<CBService: 0x1c0668580, isPrimary = YES, UUID = Heart Rate>",
* "<CBService: 0x1c06685c0, isPrimary = YES, UUID = FEE0>",
* "<CBService: 0x1c0668600, isPrimary = YES, UUID = FEE1>"
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (error)
{
NSLog(@"扫描服务失败--%@--%@", peripheral.name, error);
return;
}
NSLog(@"扫描服务成功--%@", peripheral.services);
//开始扫描服务
for (CBService *service in peripheral.services)
{
[peripheral discoverCharacteristics:nil forService:service];
}
}
/**
* 扫描到服务中的特征
* 2017-10-26 00:44:53.680903+0800 CoreBlueTooth[7135:693724] 扫描特征成功--Device Information--(
* "<CBCharacteristic: 0x1c00b9bc0, UUID = Serial Number String, properties = 0x2, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00ba640, UUID = Hardware Revision String, properties = 0x2, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00b9fe0, UUID = Software Revision String, properties = 0x2, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00b7700, UUID = System ID, properties = 0x2, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00b8180, UUID = PnP ID, properties = 0x2, value = (null), notifying = NO>"
* )
* 2017-10-26 00:44:53.775460+0800 CoreBlueTooth[7135:693724] 扫描特征成功--00001530-0000-3512-2118-0009AF100700--(
* "<CBCharacteristic: 0x1c00ba4c0, UUID = 00001531-0000-3512-2118-0009AF100700, properties = 0x18, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00b9f80, UUID = 00001532-0000-3512-2118-0009AF100700, properties = 0x4, value = (null), notifying = NO>"
* )
* 2017-10-26 00:44:53.872212+0800 CoreBlueTooth[7135:693724] 扫描特征成功--1811--(
* "<CBCharacteristic: 0x1c00b9c20, UUID = 2A46, properties = 0x8, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00ba400, UUID = 2A44, properties = 0x1A, value = (null), notifying = NO>"
* )
* 2017-10-26 00:44:53.969354+0800 CoreBlueTooth[7135:693724] 扫描特征成功--1802--(
* "<CBCharacteristic: 0x1c02a0ea0, UUID = 2A06, properties = 0x4, value = (null), notifying = NO>"
* )
* 2017-10-26 00:44:54.067643+0800 CoreBlueTooth[7135:693724] 扫描特征成功--Heart Rate--(
* "<CBCharacteristic: 0x1c02a22e0, UUID = 2A37, properties = 0x10, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c02a06c0, UUID = 2A39, properties = 0xA, value = (null), notifying = NO>"
* )
* 2017-10-26 00:44:54.465547+0800 CoreBlueTooth[7135:693724] 扫描特征成功--FEE0--(
* "<CBCharacteristic: 0x1c00b9560, UUID = Current Time, properties = 0x1A, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00b93e0, UUID = 00000001-0000-3512-2118-0009AF100700, properties = 0x14, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00b8cc0, UUID = 00000002-0000-3512-2118-0009AF100700, properties = 0x10, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00ba6a0, UUID = 00000003-0000-3512-2118-0009AF100700, properties = 0x14, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00ba700, UUID = Peripheral Preferred Connection Parameters, properties = 0x16, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00ba760, UUID = 00000004-0000-3512-2118-0009AF100700, properties = 0x14, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00ba7c0, UUID = 00000005-0000-3512-2118-0009AF100700, properties = 0x10, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00ba8e0, UUID = 00000006-0000-3512-2118-
* 2017-10-26 00:44:54.754186+0800 CoreBlueTooth[7135:693724] 扫描特征成功--FEE1--(
* "<CBCharacteristic: 0x1c00baac0, UUID = 00000009-0000-3512-2118-0009AF100700, properties = 0x16, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00baa60, UUID = FEDD, properties = 0x8, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00bac40, UUID = FEDE, properties = 0x2, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00baca0, UUID = FEDF, properties = 0x2, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00bad00, UUID = FED0, properties = 0xA, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00badc0, UUID = FED1, properties = 0xA, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00bafa0, UUID = FED2, properties = 0x2, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00bad60, UUID = FED3, properties = 0xA, value = (null), notifying = NO>",
* "<CBCharacteristic: 0x1c00bb120, UUID = 0000FEC1-0000-3512-2118-0009AF100700, properties = 0x1A, value = (null), notifying = NO>"
* )
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
if (error)
{
NSLog(@"扫描特征失败--%@--%@", service.characteristics, error);
return;
}
NSLog(@"扫描特征成功--%@--%@--%@", peripheral.name, service.UUID, service.characteristics);
//这里外设需要订阅特征的通知,否则无法收到外设发送过来的数据,需要为每一个需要读取数据的特征单独设置
// [peripheral setNotifyValue:YES forCharacteristic:service.characteristics];
// [peripheral readValueForCharacteristic:service.characteristics.firstObject];
//遍历特征
for (CBCharacteristic *characteristic in service.characteristics)
{
NSLog(@"%@", characteristic.UUID.UUIDString);
//读取步数
if ([characteristic.UUID.UUIDString isEqualToString:@"FED3"])
{
//可以通过两种方式来读取数据,方别进入两个回调
[peripheral readValueForCharacteristic:characteristic];
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
}
}
//获得从外设发来的数据,在方法"readValueForCharacteristic:"执行之后调用,
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error)
{
NSLog(@"读取特征数据失败--%@", error);
return;
}
NSLog(@"%@", characteristic);
}
//中心(本机)读取从外设发来的数据,在方法"setNotifyValue:forCharacteristic:"执行之后调用
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error)
{
NSLog(@"读取特征数据失败--%@", error);
return;
}
NSLog(@"%@", characteristic);
}
//检测数据是否写入成功
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
}