蓝牙开发中碰到的几个技术点:
0),蓝牙协议制定;
1),蓝牙密钥配对;
2),获取蓝牙Mac地址;
3),实时获取蓝牙设备信号;
4),通过蓝牙升级硬件版本;
5),通过蓝牙上传文件;
6),封装蓝牙静态包;
蓝牙协议制定:
蓝牙封包格式依次为:包头(一个字节),指令码(一个字节),封包长度(一个字节),参数(n个字节),校验码(一个字节);
例如下图:
当然还有封包格式更简单的,只有包头,指令码,和参数组成;具体什么格式要找硬件开发人员定义,蓝牙以二进制的数据流,通过广播发送至app端, app在回调方中接收的数据为NSData类型。(接收数据处理:NSData -> byte ->NSString).
包头:是硬件返回指令包首个字节的内容,可以与硬件开发人员定义好。
指令码:是区分每个指令的内容,可以与硬件开发人员定义好。
长度:封包總長度(包含包頭、校驗碼),單位為byte。
参数:依指令碼不同而定。
校验码:Checksum , 封包中除了校驗碼以外的位元組數據總和,再除以256的餘數。
蓝牙密钥配对:
蓝牙配对分为LMP配对(PIN配对)和SSP配对,前者需要输入pin码进行蓝牙配对。后者不需要输入pin码。其中具体的原理参照如下链接:https://www.jianshu.com/p/683c287fee3e。
在这里,大家普遍踩过的坑,就是配对警告框不弹出的问题。解决办法:1),要和硬件开发人员确定蓝牙设备是否发送配对广播,这个可以让硬件开发人员确定,他们手里都有蓝牙厂商提供的相应的测试app。2),最容易忽略的一点,就是代码中在初始化蓝牙中心设备和搜索蓝牙设备的时候设置options参数为@{CBCentralManagerOptionShowPowerAlertKey:@YES}。{CBCentralManagerOptionShowPowerAlertKey:@(YES)} 允许弹出警告框。 具体代码如下:
_centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:@{CBCentralManagerOptionShowPowerAlertKey:@(YES)}];
[self.centralManager scanForPeripheralsWithServices:nil options:@{CBCentralManagerOptionShowPowerAlertKey:@(YES)}];。
获取蓝牙Mac地址:
由于苹果手机的保密性,蓝牙设备通过广播携带的私有蓝牙信息会被屏蔽掉。不过还有些项目在链接蓝牙设备时,会用到蓝牙的Mac地址 蓝牙Mac地址。此时就需要获取蓝牙的Mac地址。解决办法如下:
1),不要让硬件开发人员把Mac地址写在广播字段中,应该以参数的形式添加到Advertising Packet 中(我向硬件开发人员请教了一下,官方语言:Added the mac address in Advertising Packet),传过来。iOS在 一下回调方法中解析advertisementData参数即可得到。
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber*)RSSI
解析代码,如下
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber*)RSSI
{
NSData* advertisementData = [advertisementData objectForKey:@"kCBAdvDataManufacturerData"]; //解析Mac地址, kCBAdvDataManufacturerData为advertisementData中的key值。其对应的value即为Mac地址。
NSString *value = [self hexadecimalString:advertisementData]; //获取Mac地址
}
-(NSString *)hexadecimalString:(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];
if (result.length < 4) {
return @"0000";
}
result = [result substringFromIndex:4];
return result;
}
实时获取蓝牙设备信号:
此坑比较隐蔽,按正常蓝牙扫描链接的正常流程走,是不能实时获取蓝牙设备信号的,纵观苹果官方暴漏的蓝牙API中,只有方法 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber*)RSSI 中的参数RSSI能解析出蓝牙信号。但是为节省系统资源,一般的做法是,一旦调用上述方法获取到相应的蓝牙设备并链接成功蓝牙后,就调用[self.centralManager stopScan]; 停止扫描。此时self.centralManager的isScaning的属性为false。此方法就不再调用。也就意味着,蓝牙设备信号,在发现蓝牙设备的时候,只能获取到一次。那么怎么才能做到事实获取信号呢?当然是在链接成功蓝牙后不要调用[self.centralManager stopScan];
方法 - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber*)RSSI就会一直回调,那么在此方法中解析出来你想要的蓝牙设备信号,展示在View上就OK了。