蓝牙模式简介
蓝牙开发分为两种模式,中心模式(central),和外设模式(peripheral)。一般来讲,我们需要在软件内连接硬件,通过连接硬件给硬件发送指令以完成一些动作的蓝牙开发都是基于中心模式(central)模式的开发,也就是说我们开发的app是中心,我们要连接的硬件是外设。如果需要其他设备连接手机蓝牙,并对手机进行一些操作,那就是基于外设模式(peripheral)的开发。 本次我们主要介绍的就是中心模式的蓝牙开发
设备简介
中心设备(CBCentralManager):iOS系统的手机等设备
外围设备(CBPeripheral):手环等第三方设备
蓝牙数据传输简介
将外围设备(车辆)的数据传送给中心设备(手机)时, 数据是经过两层包装的
第一层是 Service(服务) , 可以是一个或多个, 比如车辆数据(服务)
第二层是 Characteristic(特征) , 他提供了更多关于Service(服务)的数据, 例如车辆数据(服务)中包含了两个数据, 分别是里程数据和续航数据, 这两个就是车辆数据(服务)的具体数据(特征)
具体操作简介
读(read) , 写(write) , 订阅(notify)
我们的目的是读取设备中的数据(read) , 或者给设备写入一定的数据(write)。有时候我们还想设备的数据变化的时候不需要我们手动去读取这个值,需要设备自动通知我们它的值变化了,值是多少。把值告诉app,这个时候就需要订阅这个特征了(notify)
其他相关概念
自己实践的两个蓝牙demo:
CoreBluetooth框架的核心其实是两个东西,peripheral和central, 可以理解成外设和中心。对应他们分别有一组相关的API和类
//objcetive c特征的定义枚举
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
};
建立中心角色
扫描外设(discover)
连接外设(connect)
扫描外设中的服务和特征(discover)
4.1 获取外设的services
4.2 获取外设的Characteristics,获取Characteristics的值,获取Characteristics的 Descriptor和Descriptor的值
与外设做数据交互(explore and interact)
订阅Characteristic的通知
断开连接(disconnect)
启动一个Peripheral管理对象
本地Peripheral设置服务,特性,描述,权限等等
Peripheral发送广告
设置处理订阅、取消订阅、读characteristic、写characteristic的委托方法
蓝牙设备状态
待机状态(standby):设备没有传输和发送数据,并且没有连接到任何设
广播状态(Advertiser):周期性广播状态
扫描状态(Scanner):主动寻找正在广播的设备
发起链接状态(Initiator):主动向扫描设备发起连接。
主设备(Master):作为主设备连接到其他设备。
从设备(Slave):作为从设备连接到其他设备。
蓝牙设备的五种工作状态
准备(standby)
广播(advertising)
监听扫描(Scanning
发起连接(Initiating)
已连接(Connected)
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
/**
* -- 初始化成功自动调用
* -- 必须实现的代理,用来返回创建的centralManager的状态。
* -- 注意:必须确认当前是CBCentralManagerStatePoweredOn状态才可以调用扫描外设的方法:
scanForPeripheralsWithServices
*/
- (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:
NSLog(@">>>CBCentralManagerStatePoweredOff");
break;
case CBCentralManagerStatePoweredOn:
{
NSLog(@">>>CBCentralManagerStatePoweredOn");
// 开始扫描周围的外设。
/*
-- 两个参数为Nil表示默认扫描所有可见蓝牙设备。
-- 注意:第一个参数是用来扫描有指定服务的外设。然后有些外设的服务是相同的,比如都有FFF5服务,那么都会发现;而有些外设的服务是不可见的,就会扫描不到设备。
-- 成功扫描到外设后调用didDiscoverPeripheral
*/
[self.centralManager scanForPeripheralsWithServices:nil options:nil];
}
break;
default:
break;
}
}
#pragma mark 发现外设
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary*)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"Find device:%@", [peripheral name]);
if (![_deviceDic objectForKey:[peripheral name]]) {
NSLog(@"Find device:%@", [peripheral name]);
if (peripheral!=nil) {
if ([peripheral name]!=nil) {
if ([[peripheral name] hasPrefix:@"根据设备名过滤"]) {
[_deviceDic setObject:peripheral forKey:[peripheral name]];
// 停止扫描, 看需求决定要不要加
// [_centralManager stopScan];
// 将设备信息传到外面的页面(VC), 构成扫描到的设备列表
if ([self.delegate respondsToSelector:@selector(dataWithBluetoothDic:)]) {
[self.delegate dataWithBluetoothDic:_deviceDic];
}
}
}
}
}
}
// 连接设备(.h中声明出去的接口, 一般在点击设备列表连接时调用)
- (void)connectDeviceWithPeripheral:(CBPeripheral *)peripheral
{
[self.centralManager connectPeripheral:peripheral options:nil];
}
#pragma mark 连接外设--成功
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
//连接成功后停止扫描,节省内存
[central stopScan];
peripheral.delegate = self;
self.peripheral = peripheral;
//4.扫描外设的服务
/**
-- 外设的服务、特征、描述等方法是CBPeripheralDelegate的内容,所以要先设置代理peripheral.delegate = self
-- 参数表示你关心的服务的UUID,比如我关心的是"FFE0",参数就可以为@[[CBUUID UUIDWithString:@"FFE0"]].那么didDiscoverServices方法回调内容就只有这两个UUID的服务,不会有其他多余的内容,提高效率。nil表示扫描所有服务
-- 成功发现服务,回调didDiscoverServices
*/
[peripheral discoverServices:@[[CBUUID UUIDWithString:@"你要用的服务UUID"]]];
if ([self.delegate respondsToSelector:@selector(didConnectBle)]) {
// 已经连接
[self.delegate didConnectBle];
}
}
#pragma mark 连接外设——失败
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"%@", error);
}
#pragma mark 取消与外设的连接回调
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
NSLog(@"%@", peripheral);
}
#pragma mark 发现服务回调
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
//NSLog(@"didDiscoverServices,Error:%@",error);
CBService * __nullable findService = nil;
// 遍历服务
for (CBService *service in peripheral.services)
{
//NSLog(@"UUID:%@",service.UUID);
if ([[service UUID] isEqual:[CBUUID UUIDWithString:@"你要用的服务UUID"]])
{
findService = service;
}
}
NSLog(@"Find Service:%@",findService);
if (findService)
[peripheral discoverCharacteristics:NULL forService:findService];
}
#pragma mark 发现特征回调
/**
-- 发现特征后,可以根据特征的properties进行:读readValueForCharacteristic、写writeValue、订阅通知setNotifyValue、扫描特征的描述discoverDescriptorsForCharacteristic。
**/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"你要用的特征UUID"]]) {
/**
-- 读取成功回调didUpdateValueForCharacteristic
*/
self.characteristic = characteristic;
// 接收一次(是读一次信息还是数据经常变实时接收视情况而定, 再决定使用哪个)
// [peripheral readValueForCharacteristic:characteristic];
// 订阅, 实时接收
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
// 发送下行指令(发送一条)
NSData *data = [@"硬件工程师给我的指令, 发送给蓝牙该指令, 蓝牙会给我返回一条数据" dataUsingEncoding:NSUTF8StringEncoding];
// 将指令写入蓝牙
[self.peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithResponse];
}
/**
-- 当发现characteristic有descriptor,回调didDiscoverDescriptorsForCharacteristic
*/
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
#pragma mark - 获取值
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
// characteristic.value就是蓝牙给我们的值(我这里是json格式字符串)
NSData *jsonData = [characteristic.value dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *dataDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
// 将字典传出去就可以使用了
}
#pragma mark - 中心读取外设实时数据
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (characteristic.isNotifying) {
[peripheral readValueForCharacteristic:characteristic];
} else {
NSLog(@"Notification stopped on %@. Disconnecting", characteristic);
NSLog(@"%@", characteristic);
[self.centralManager cancelPeripheralConnection:peripheral];
}
}
// 上文中发现特征之后, 发送下行指令的时候其实就是向蓝牙中写入数据
// 例:
// 发送检查蓝牙命令
- (void)writeCheckBleWithBle
{
_style = 1;
// 发送下行指令(发送一条)
NSData *data = [@"硬件工程师提供给你的指令, 类似于5E16010203...这种很长一串" dataUsingEncoding:NSUTF8StringEncoding];
[self.peripheral writeValue:data forCharacteristic:self.characteristic type:CBCharacteristicWriteWithResponse];
}
#pragma mark 数据写入成功回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"写入成功");
if ([self.delegate respondsToSelector:@selector(didWriteSucessWithStyle:)]) {
[self.delegate didWriteSucessWithStyle:_style];
}
}
#pragma mark 停止扫描外设
- (void)stopScanPeripheral{
[self.centralManager stopScan];
}
#pragma mark 扫描外设
- (void)scanDevice
{
if (_centralManager == nil) {
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[_deviceDic removeAllObjects];
}
}
#pragma mark 断开连接
- (void)disConnectPeripheral{
/**
-- 断开连接后回调didDisconnectPeripheral
-- 注意断开后如果要重新扫描这个外设,需要重新调用[self.centralManager scanForPeripheralsWithServices:nil options:nil];
*/
[self.centralManager cancelPeripheralConnection:self.peripheral];
}