iOS 蓝牙 BlueTooth BLE
因为工作的需要, 研究了下iOS端蓝牙方面的知识. 研究的并不深入. 只做到了两台设备互相通讯的程度. 能够满足一般项目需求.
蓝牙基础知识(FAQ)
有经典蓝牙(classic)和低功耗蓝牙(Bluetooth Low Energy BLE).
功能机时代的蓝牙非常耗电. 能待机好几天的手机开启蓝牙几个小时就没电了... 但是蓝牙4.0(BLE) 大大降低了蓝牙的耗电量.
iOS端蓝牙通信框架CoreBlueTooth. 在iPhone4s开始支持,专门用于与BLE设备通讯.
经典蓝牙和BLE的流程是不同的.
经典模式 1.扫描设备. 2.建立设备连接. 3.建立socket连接. 4.发送和接收数据. 5.通讯完毕关闭连接清理缓存.
BLE 比经典模式复杂很多. 下面会详细介绍.
BLE简介
BLE下 设备有四种角色. 这里只介绍两种. 中心设备(center)和外围设备(peripheral)
中心设备负责扫描外围设备. 当扫描到外围设备后主动发起连接. 连接成功后扫描外围设备的服务和特征. 根据服务和特征进行通信.
外围设备负责广播自己的服务和特征.
服务和特征
服务(service) 特征(characteristic)
每个蓝牙4.0的设备都是通过服务和特征来展示自己的. 一个设备必然包含一个或多个服务,每个服务下面又包含若干个特征. 特征是与外界交互的最小单位.
例如特征A用于写数据. 特征B用来读数据.
服务和特征都是用UUID来标示自己. (此处UUID只是一种唯一标示符. 和iOS真机调试, AdHoc打包的UUID没什么关系.他们都是一种唯一标示符)
有一些国际通用的服务UUID . 这些在CoreBlueTooth框架中可以找到(宏定义).
当然你也可以使用自己独特的UUID. 使用UUIDDesign生成.
中心设备与外围设备如何通讯.
通讯无非是读(read)和写(write). 但是在BLE中. 读和写都是由中心设备发起的. 中心设备可以向外围设备发起写请求(request中包含写入的数据. ) 也可以像外围设备发起读请求(response中包含外围设备写入的数据.)
还有一种比较特殊. notify 也可以称为订阅模式. 订阅模式是中心设备订阅外围设备的某个特征. 当外围设备更新被订阅的特征时. 中心设备会收到更新的信息. (这种模式一次只能传输20个byte)
编码
中心设备模式
创建中心设备对象
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
检测当前设备是否已经开启蓝牙. 开启蓝牙后开始扫描外围设备
扫描设备时不指定UUID. 会扫描出所有设备. 指定UUID.则只会扫描到提供指定UUID服务的设备.(也就是提供某种指定服务的设备, 如果你不希望扫描到乱七八糟的设备可以自己生成UUID.)
[self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] options:@{ CBCentralManagerScanOptionAllowDuplicatesKey : @YES }];
当扫描到外围设备时自动调用这个方法, 在这个方法中连接外围设备(连接设备是有可能失败的. 连接失败有一个单独的回调方法.).
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
// 判断信号强度
if (RSSI.integerValue > -15) {
return;
}
if (RSSI.integerValue < -35) {
return;
}
//由于本方法会被反复调用. 所以已经连接过的设备就不用重复connect了
if (self.discoveredPeripheral != peripheral) {
self.discoveredPeripheral = peripheral;
[self.centralManager connectPeripheral:peripheral options:nil];
}
}
connect成功会调用这个方法
连接成功后扫描这台设备提供的服务.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
//连接成功停止扫描(如果做一对多的情况, 不停止扫描)
[self.centralManager stopScan];
peripheral.delegate = self;
//扫描服务.
[peripheral discoverServices:@[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]]];
}
扫描到指定服务后调用这个方法
扫描到想要的服务后查看这个服务中的各个特征, 寻找想要的特征并且把特征对象存起来!
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
{
if (error) {
return;
}
//下面扫描了两个特征.
for (CBService *service in peripheral.services) {
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]] forService:service];
[peripheral discoverCharacteristics:@[[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID_CENTER_WRITE]] forService:service];
}
}
扫描到指定特征后调用这个方法. 扫描到想要的特征后. 订阅它或者向它们发送读请求或者写请求.
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
{
for (CBCharacteristic *characteristic in service.characteristics) {
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID_CENTER_WRITE]]) {
//这是一个拥于向外围设备写数据的特征
self.writeCharacteristic = characteristic;
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
//这个特征用于订阅外围设备.
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]]) {
self.transferCharacteristic = characteristic;
[peripheral setNotifyValue:YES forCharacteristic:characteristic];
[peripheral discoverDescriptorsForCharacteristic:characteristic];
}
}
}
订阅模式, 当外围设备更新特征时这个方法会被调用.
用这种模式可以很方便的实现一对多通讯. 一个外围设备同时连接多台中心设备. 当外围设备更新特征.所有中心设备都会收到更新的信息.(一次只能传20个字节, 我没有试过传超过20个字节会发生什么. 如果有人试验了请给我回信. 谢谢.)
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error
{
if (error) {
NSLog(@"Error discovering characteristics: %@", [error localizedDescription]);
return;
}
NSString *stringFromData = [[NSString alloc] initWithData:characteristic.value encoding:NSUTF8StringEncoding];
if ([stringFromData isEqualToString:@"EOM"]) {
[self.data resetBytesInRange:NSMakeRange(0, self.data.length)];
[self.data setLength:0];
return ;
}
[self.data appendData:characteristic.value];
}
向外围设备的某个特征写数据.
//data是字节数据.
[self.discoveredPeripheral writeValue:data forCharacteristic:self.writeCharacteristic type:CBCharacteristicWriteWithResponse];
外围设备模式
创建外围设备对象
self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
创建服务和特征
//这是一个用于中心设备订阅外围设备信息的特征
self.transferCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID]
properties:CBCharacteristicPropertyNotify
value:nil
permissions:CBAttributePermissionsReadable];
//这是一个用于中心设备向外围设备写数据的特征.
self.receiveDataCharacteristic = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_CHARACTERISTIC_UUID_CENTER_WRITE]
properties:CBCharacteristicPropertyWrite value:nil permissions:CBAttributePermissionsWriteable];
//创建服务
CBMutableService *transferService = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]
primary:YES];
transferService.characteristics = @[self.transferCharacteristic, self.receiveDataCharacteristic];
//将服务添加到外围设备对象中
[self.peripheralManager addService:transferService];
开启广播advertising
[self.peripheralManager startAdvertising:@{ CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:TRANSFER_SERVICE_UUID]] }];
当有中心设备订阅了外围设备的特征时这个方法会被调用
//当中心设备订阅了某个特征时 调用这个方法.
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
{
NSString *string = @"这一条是外围设备发送的测试数据,, 当中心设备订阅了外围设备特征的时候就会自动发送这段话.end";
self.dataToSend = [string dataUsingEncoding:NSUTF8StringEncoding];
[self sendData];
}
总结
通信无非是读和写
-
中心设备向外围设备写数据. 需要外围设备提供可以接收写请求的特征. 中心设备向特征写数据.
-
中心设备读外围设备的数据. 外围设备提供可以接收读请求的特征. 中心设备向特征发送读请求.
-
外围设备向中心设备写数据. 这个可以用订阅模式. 如果中心设备订阅了特征. 外围设备更新特征即向中心设备发送了数据.
-
外围设备读中心设备的数据. 恩这个情况好像只能由中心设备发起吧...
中心设备向外围设备发送写请求(也就是上面的第一种情况)
<a name="UUIDdesign-zd">UUIDDesign</a>
ARC下生成一个UUID
- (NSString *)gen_uuid
{
CFUUIDRef uuid_ref = CFUUIDCreate(NULL);
CFStringRef uuid_string_ref= CFUUIDCreateString(NULL, uuid_ref);
CFRelease(uuid_ref);
NSString *uuid = [NSString stringWithString:(__bridge NSString*)uuid_string_ref];
CFRelease(uuid_string_ref);
return uuid;
}
转发请注明出处(简书 行如风).
网友评论