iOS 蓝牙开发
2019-03-18 本文已影响0人
aCodeApe
自己之前做的蓝牙项目,刚开始的时候真是一脸蒙圈,摸索了很久踩了很多坑。不过现在想来其实也没什么难的,简单总结一下,但愿能够给初次开发的小伙伴一些帮助,当然如果有不足之处,也欢迎大家指正。
-
CoreBluetooth框架简单介绍
-
CBCentralManager
中心管理者——核心角色,用于扫描外设等 -
CBCentralManagerState
蓝牙状态——CBCentralManagerStatePoweredOn
表示可用 -
CBPeripheral
外设 -
CBService
服务信息——可以拿到服务的UUID -
CBCharacteristic
特征值——包括读、写特征,用于读取或写入数据 -
CBDescriptor
描述值——描述特征值的信息或属性
-
-
外设的基本信息
-
advertisementData
广播包——是一个字典,存储设备相关信息 -
kCBAdvDataIsConnectable
连接外设的状态——1
表示已连接 -
kCBAdvDataManufacturerData
厂家提供的数据——会含有外设的Mac地址,开发中重点看这里的数据 -
kCBAdvDataServiceUUIDs
服务的所有UUID——数组 -
RSSI
——信号强度
-
-
连接外设的流程及Mac地址
- 流程描述
1、建立中心角色:CBCentralManager
2、扫描外设:discoverPeripheral
3、发现并连接外设:connectPeripheral
4、扫描外设中的服务:discoverServices
5、根据服务去获取特征及相关描述:discoverCharacteristicsForService
6、订阅Characteristic的通知:setNotifyValue
7、根据特征值进行相关数据的读写操作:writeValue | characteristic.value
8、断开连接:disConnect - 关于Mac地址
蓝牙外设的Mac地址是一个物理地址,每个设备都是唯一的。我们在蓝牙开发的时候需要用到这个地址来连接外设,但是CoreBluetooth
框架对这个进行了封装,没有暴露出来,只是给我们提供了一个UUID,可是这个UUID在实际情况中并不实用。询问Android同事了解到他们是可以获取到Mac地址的,并且能够通过Mac地址直接连接设备,所以我们iOS端只能自己想办法获取外设的Mac地址。通常来说,硬件工程师都会将设备的Mac地址(16进制)写入到广播包中,我们可以轻松拿到(如果没有的话,那你可能需要联系设备厂商了)。在连接外设的时候我的做法是根据后台提供的Mac地址与从外设广播包中获取Mac地址进行匹配。
请看代码:
- 流程描述
//获取广播包中的数据
NSData *data = advertisementData[@"kCBAdvDataManufacturerData"];
//格式转换
NSString *str = [self transformStringWithData:data];
//mac地址匹配,连接设备,self.peripheralMacAddress即从后台获取的Mac地址
if ([[self macAddressWith:str] isEqualToString:self.peripheralMacAddress]) {
//匹配成功后进行外设连接操作
}
//对广播包中的数据进行格式转换
- (NSString *)transformStringWithData:(NSData *)data{
NSString *result;
const unsigned char *dataBuffer = (const unsigned char *)[data bytes];
if (!dataBuffer) {
return nil;
}
NSUInteger dataLength = [data length];
NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
for (int i = 0; i < dataLength; i++) {
//02x 表示两个位置 显示的16进制
[hexString appendString:[NSString stringWithFormat:@"%02lx",(unsigned long)dataBuffer[i]]];
}
result = [NSString stringWithString:hexString];
return result;
}
//获取外设Mac地址
- (NSString *)macAddressWith:(NSString *)aString{
NSMutableString *macString = [[NSMutableString alloc] init];
if (aString.length >= 16) {
[macString appendString:[[aString substringWithRange:NSMakeRange(4, 2)] uppercaseString]];
[macString appendString:@":"];
[macString appendString:[[aString substringWithRange:NSMakeRange(6, 2)] uppercaseString]];
[macString appendString:@":"];
[macString appendString:[[aString substringWithRange:NSMakeRange(8, 2)] uppercaseString]];
[macString appendString:@":"];
[macString appendString:[[aString substringWithRange:NSMakeRange(10, 2)] uppercaseString]];
[macString appendString:@":"];
[macString appendString:[[aString substringWithRange:NSMakeRange(12, 2)] uppercaseString]];
[macString appendString:@":"];
[macString appendString:[[aString substringWithRange:NSMakeRange(14, 2)] uppercaseString]];
}
EBLog(@"macString:%@",macString);
return macString;
}
-
部分相关代码(代理方法)
- CBCentralManagerDelegate
// 当状态更新时调用(如果不实现会崩溃)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central.state) {
case CBCentralManagerStateUnknown:
EBLog(@"蓝牙状态-> 未知");
break;
case CBCentralManagerStateResetting:
EBLog(@"蓝牙状态-> 重置");
break;
case CBCentralManagerStateUnsupported:
EBLog(@"蓝牙状态-> 不支持");
break;
case CBCentralManagerStateUnauthorized:
EBLog(@"蓝牙状态-> 未授权");
break;
case CBCentralManagerStatePoweredOff:
EBLog(@"蓝牙状态-> 关闭");
break;
case CBCentralManagerStatePoweredOn:
EBLog(@"蓝牙状态-> 可用");
break;
default:
break;
}
}
/**
发现设备
@param central 中心管理者
@param peripheral 扫描到的设备
@param advertisementData 广告信息
@param RSSI 信号强度
*/
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI{
EBLog(@"发现设备->\n name=%@\n advertisementData=%@\n UUIDString=%@\n services=%@\n 信号->%@",peripheral.name,advertisementData,peripheral.identifier.UUIDString,peripheral.services,RSSI);
NSData *data = advertisementData[kAdDataKey];
NSString *str = [HandleDataManager transformStringWithData:data];
if ([[self macAddressWith:str] isEqualToString:self.peripheralMacAddress]) {
//停止扫描
[self.centralManager stopScan];
//连接外设
self.peripheral = peripheral;
[self.centralManager connectPeripheral:self.peripheral options:nil];
//将外设对象添加到数组中
if (![self.peripheralArray containsObject:peripheral]){
[self.peripheralArray addObject:self.peripheral];
}
}
}
/**
连接成功
@param central 中心管理者
@param peripheral 连接成功的设备
*/
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
EBLog(@"连接设备成功-> %@",peripheral.name);
//停止扫描
[self.centralManager stopScan];
//设置peripheral代理
self.peripheral.delegate = self;
//扫描服务操作
......
}
/**
连接失败
@param central 中心管理者
@param peripheral 连接失败的设备
@param error 错误信息
*/
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
if (error) {
EBLog(@"连接失败-> %@",error.description);
}
}
/**
连接断开
@param central 中心管理者
@param peripheral 连接断开的设备
@param error 错误信息
*/
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error{
if (error) {
EBLog(@"连接断开-> %@",error.description);
}
}
- CBPeripheralDelegate
/**
扫描到服务
@param peripheral 服务对应的设备
@param error 扫描错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
for (CBService *service in peripheral.services) {
EBLog(@"服务UUID: %@",service.UUID);
// 获取对应的服务
if ([service.UUID.UUIDString isEqualToString:@"厂商提供的服务ID"]){
// 根据服务去扫描特征
[self.peripheral discoverCharacteristics:nil forService:service];
}
}
}
/**
扫描到对应的特征
@param peripheral 设备
@param service 特征对应的服务
@param error 错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
EBLog(@"发现特征值=%@", service.characteristics);
if (!error) {
// 遍历所有的特征
[service.characteristics enumerateObjectsUsingBlock:^(CBCharacteristic * _Nonnull characteristic, NSUInteger idx, BOOL * _Nonnull stop) {
EBLog(@"特征的读写等属性=%lu", (unsigned long)characteristic.properties);
if (characteristic.properties == CBCharacteristicPropertyNotify) {
// 订阅, 实时接收
[self.peripheral setNotifyValue:YES forCharacteristic:characteristic];
}
else if (characteristic.properties == CBCharacteristicPropertyWrite){
self.writeCharacteristic = characteristic;
}
}];
}
}
/**
根据特征读到数据
@param peripheral 读取到数据对应的设备
@param characteristic 特征
@param error 错误信息
*/
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
EBLog(@"didUpdateValue 特征值=%@",characteristic);
if (error) {
EBLog(@"特征值=%@ == error %@",characteristic.UUID, error);
}
else{
EBLog(@"收到特征值=%@ updated 发来的数据: %@", characteristic.UUID, characteristic.value);
if (characteristic.value) {
//读取到相关数据的操作
}
}
}
//更新描述值的时候会调用
- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForDescriptor:(CBDescriptor *)descriptor error:(NSError *)error {
EBLog(@"描述(%@)",descriptor.description);
}
//发现外设的特征的描述数组
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
// 在此处读取描述即可
for (CBDescriptor *descriptor in characteristic.descriptors) {
EBLog(@"发现外设的特征descriptor(%@)",descriptor);
[self.peripheral readValueForDescriptor:descriptor];
}
}
//写入指令是否成功回调
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(nullable NSError *)error{
if (error) {
EBLog(@"特征值=%@ 写入失败 error %@",characteristic.UUID,error.description);
}
else{
EBLog(@"特征值=%@ 写入成功",characteristic.UUID);
}
}
- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
EBLog(@"通知发生改变->特征值=%@,isNotifying=%@",characteristic.UUID,characteristic.isNotifying?@"YES":@"NO");
//订阅成功
if (characteristic.isNotifying == YES) {
}
}
-
BabyBluetooth蓝牙库
BabyBluetooth这个库采用的链式编程思想,其中的block方法,可以重新按照功能和顺序组织代码,并提供许多方法减少蓝牙开发过程中的代码量。
详细介绍参考链接
BabyBluetooth蓝牙库介绍
BabyBluetooth源码GitHub地址
-
自己封装的蓝牙库
ZYBluetoothManager是一个轻量级的蓝牙工具类,简单实用,可以满足基本的需求。细节及其他功能还在不断完善与优化中,也欢迎各位大神给出宝贵意见。
源码GitHub地址:ZYBluetoothManager