蓝牙指令的收发
最近人有点堕落,好久没有更新东西了,一直负责维护开发公司的蓝牙项目,借此整理一下关于蓝牙的相关资料信息。
最开始,做蓝牙的时候感觉这个技术还挺low的,距离受限制传输效率也受限制,直到最近有个科技展展示了一些用蓝牙技术开发的智能硬件,才发现在这个物联网被炒的热火朝天的硬件市场,蓝牙还是能占有一席之地的~
想要实现蓝牙连接首先第一步就是导入头文件
1.导入头文件#import <CoreBluetooth/CoreBluetooth.h>
2.设置中心及外设的属性
@property(nonatomic,strong)CBCentralManager*cbCentralMgr;//管理中心(发起连接)
@property(nonatomic,strong)CBPeripheral*myPeripheral;//外部设备(被动连接)
3.继承代理方法<CBCentralManagerDelegate,CBPeripheralDelegate>
4.创建中心设备的实例并设置代理
self.cbCentralMgr= [[CBCentralManageralloc]initWithDelegate:selfqueue:nil];
5.到了这一步开始就要进入真正的干货,中心管理设置delegate后会自动调用本机蓝牙状态的方法也就是
- (void)centralManagerDidUpdateState:(CBCentralManager *)central;//这个方法
这里: central.state 有多种状态
其中我们最主要使用的是两种CBManagerStatePoweredOff,CBManagerStatePoweredOn, //关闭状态,
蓝牙开启
具体区分可以考虑下面使用模式
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
switch (central.state)
{
case CBCentralManagerStatePoweredOff:
NSLog(@"Bluetooth is currently powered off.");
// 这里就是蓝牙未开启,我们可以在这个位置给用户个提示什么的
break;
case CBCentralManagerStatePoweredOn:
{
NSLog(@"CBCentralManagerStatePoweredOn");
// 这个地方就是蓝牙已开启,我们可以使用[self.cbCentralMgr scanForPeripheralsWithServices:nil options:nil]; 这个方法进行扫描周边设备设备
}
break;
default:
break;
}
}
6.一旦扫描到设备就会自动调用代理方法
//发现蓝牙设备,可能发现不止一个蓝牙设备,所以该方法可能被调用多次
- (void)centralManager:(CBCentralManager*)central didDiscoverPeripheral:(CBPeripheral*)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber*)RSSI{}
在这里,符合我们条件的可以进行蓝牙连接操作(手动调用)
- (void)connectPeripheral:(CBPeripheral *)peripheral options:(nullable NSDictionary<NSString *, id> *)options;
7.在扫描到一个设备时,我们可以进行外部设备和中心的连接
[self.cbCentralMgr connectPeripheral:peripheral options:[NSDictionary dictionaryWithObject:[NSNumbernumberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
在连接的时候同时可以关闭中心的扫描
[self.cbCentralMgrstopScan];
8.在中心和外部设备的连接过程中,可能会调用以下几个方法:
当连接成功时调用当连接上某个蓝牙之后,CBCentralManager会通知代理处理
- (void)centralManager:(CBCentralManager*)central didConnectPeripheral:(CBPeripheral*)peripheral{}
当连接失败时调用 当连接蓝牙失败的时候会调用
- (void)centralManager:(CBCentralManager*)central didFailToConnectPeripheral:(CBPeripheral*)peripheral error:(NSError*)error{}
当连接断开时调用 当中心主动与外设断开或外设主动和中心断开成功时调用
- (void)centralManager:(CBCentralManager*)central didDisconnectPeripheral:(CBPeripheral*)peripheral error:(nullableNSError*)error{}
(注意:当我们连接成功后,要为外设设置代理 peripheral.delegate=self;)
到目前为止,上面所有的代理(我说的是代理方法,代理里面调用的方法要自己写)方法,都是cbCentralMgr(管理中心的代理方法),满足条件就会调用.当连接成功之后,我们设置好设备代理,下面开始就是设备代理的方法
(管理中心 的代理方法都是以centralManager 开头,而外设的代理方法都是以peripheral,还是比较好区分)
9.查询所有服务时会调用外设的方法
//返回的蓝牙服务通知通过代理实现
- (void)peripheral:(CBPeripheral*)peripheral didDiscoverServices:(NSError*)error
{
for (CBService* service in peripheral.services){
// 但是服务并不是我们的目标,也没有实际意义。我们需要用的是服务下的特征,查询(每一个服务下的若干)特征(下面那段知道服务和特征的小伙伴可以直接跳过)
// 什么是服务和特征:每个蓝牙4.0的设备都是通过服务和特征来展示自己的,一个设备必然包含一个或多个服务,每个服务下面又包含若干个特征。特征是与外界交互的最小单位。
[peripheral discoverCharacteristics:nil forService:service];
}
}
10.在这个方法里会进行扫描所有特征值,进而调用外设查询特征值的代理方法
//返回的蓝牙特征值通知通过代理实现(不是所有的特征我们都需要,具体的要根据需求做区分,要跟硬件或者厂家沟通)
- (void)peripheral:(CBPeripheral*)peripheral didDiscoverCharacteristicsForService:(CBService*)service error:(NSError*)error{
}
在上面的方法中可以获取到已经与中心连接的外设内有哪些特征值,并且可以通过打开通知来读取特征值内数据的变化
[peripheral setNotifyValue:YES forCharacteristic:characterstic];
11.特征值内数据变化时会调用外设的代理方法
- (void) peripheral:(CBPeripheral*)aPeripheral didUpdateValueForCharacteristic:(CBCharacteristic*)characteristic error:(NSError*)error{}
12.在该方法里面读取的特征值内容是NSData类型,我们也可以转换为字节数组进行判断
NSData*data = characteristic.value;
//data转byte数组
Byte*testByte = (Byte*)[databytes];
for(inti=0;i<[datalength]; i++){
if((testByte[1] ==2)&&(testByte[0] ==2)) {
//做简单的演示判断
}
}
// 如果我们想给蓝牙设备发送指令就要执行下面的方法(手动执行)
[peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
------------------------- 分割线 干货来了 -------------------------
OK 到了这里基本就够用了,我们开发基本就是连接蓝牙接受发送命令,上面的内容已经被无数的人写烂了,如果就这样我都不带写的!我最主要想写的是蓝牙的交互!
首先我们要明确的是蓝牙发送过来的基本是以广播包的形式发送过来的(广播不理解的自行百度),以我们现在的项目为例,当蓝牙发送过来的广播包包含的内容如下
是不是有些看不懂了,广播包中的数据为byte数组 A1 表示的就是接收数据(我们公司定义的是这个也可以是其他的),后面的图上都有解释,因为我们收到蓝牙发送的时候是NSdata类型的数据,所以我们要把它转化成byte类型,这样我们才可以进一步操作了
Byte *testByte = (Byte *)[data bytes]; // 可以使用这个方法转换
以这个图片汇总的内容为例:
testByte[2] 表示的就是控制器状态1
textByte[3] 表示的就是控制器状态2
.......
到了这一步还有一点点的坑需要解决就是这下面的bit 0 bit 1 ....... 这些东西是啥? 这个就是控制器状体蓝牙发送过来的byte类型中的这几个元素是以16进制发送过来的,当我们把它转换成二进制就是下面的这个样子
4035ECC6-77DC-4988-9D22-36C1EA2FD3C1.png 4B2D7D24-87C1-45AE-A552-E651DE193827.png这回能看懂了吗十六进制转换成二进制就是这个样子,二进制一共八位与之相对应的就是图片中的状态
例如: 图一中 二进制位为11011110 对照图二
二进制第一位为 1 表示电机缺项 (如果为0 表示 不缺项)
二进制第二位为 1 暂无状态表示
二进制第三位为 0 表示不再巡航状态
......
一个控制器状态要传两个byte,因为一个byte能存储的最大为 255 也就是16进制的 FF 超过这个就要丢失数据了所以我们要传两个byte 控制器状态,如果你们公司的硬件给力其他的一些数据给你发过来的基本就可以直接展示不需要转化(我们的硬件就挺给力的,忘了那些不需要转换了)
下面我们开始发送数据
如图其他的都还好,如驱动方式这个直接发送0 1 2 都行没什么需要注意的,但是这个控制器控制设定需要我们注意一下,因为这个也是个二进制需要我们转换一下在发送
这里我手动发送一个指令出去
// 176 十六进制 B1 (这个是发送的指令)
// 0 第二位没确定意义
// 255 0xff 就是 255
// 1 代表确定方式设定
// 0 设置值 (当超过255 的时候这一位就起作用了)
// 1 设置值 (1 代表电驱动)
Byte byteData[6] = {176,0,255,1,0,1};
// 这个byte的意思就是发送指令给蓝牙 驱动方式为电驱动
// 176 十六进制 B1 (这个是发送的指令)
// 0 第二位没确定意义
// 255 0xff 就是 255
// 5 代表轮径 (就是图中的0x05)
// 255 设置值 (当超过255 的时候这一位就起作用了)
// 100 设置值 (1 代表电驱动)
当轮径为355 的时候,一个字节不够用,所以前面一位备用这个时候正好使用,255 后面一位则为剩下的值 100 合起来拼接之后就是 355 (硬件那边会处理)
//Byte byteData[6] = {176,0,255,5,255,100}; // 说的就是这里的 255 和100 最后两位
NSData * data = [NSData dataWithBytes:byteData length:6];
[peripheral writeValue:data forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
---------------------------------- 更新----------------------------------
再给大家来点干货进制转换,蓝牙发送过来的数据基本以十六进制为主,而我们要给蓝牙发送的命令也需要转换成16进制,有些展示内容还是二进制的,所以进制转换必不可少.这里给大家整理了一下常用的进制转换,这些都是经过实战考验的,项目代码就不能给大家分享了,不过到这里差不多蓝牙开发需要的大多数东西都已经OK了,剩下的可能就是大家公司具体要求不同的那些细节!(这里的代码可以直接拷贝使用)
/**二进制转十进制*/
+ (NSString *)toDecimalSystemWithBinarySystem:(NSString *)binary
{
int ll = 0 ;
int temp = 0 ;
for (int i = 0; i < binary.length; i ++)
{
temp = [[binary substringWithRange:NSMakeRange(i, 1)] intValue];
temp = temp * powf(2, binary.length - i - 1);
ll += temp;
}
NSString * result = [NSString stringWithFormat:@"%d",ll];
return result;
}
/**十进制转二进制*/
+ (NSString *)toBinarySystemWithDecimalSystem:(NSInteger)decimal
{
NSInteger num = decimal;//[decimal intValue];
NSInteger remainder = 0; //余数
NSInteger divisor = 0; //除数
NSString * prepare = @"";
while (true)
{
remainder = num%2;
divisor = num/2;
num = divisor;
prepare = [prepare stringByAppendingFormat:@"%ld",remainder];
if (divisor == 0)
{
break;
}
}
NSString * result = @"";
for (NSInteger i = prepare.length - 1; i >= 0; i --)
{
result = [result stringByAppendingFormat:@"%@",
[prepare substringWithRange:NSMakeRange(i , 1)]];
}
return result;
}
//将十进制转化为十六进制
+ (NSString *)ToHex:(int)tmpid
{
NSString *nLetterValue;
NSString *str =@"";
long long int ttmpig;
for (int i = 0; i<9; i++) {
ttmpig=tmpid%16;
tmpid=tmpid/16;
switch (ttmpig)
{
case 10:
nLetterValue =@"a";break;
case 11:
nLetterValue =@"b";break;
case 12:
nLetterValue =@"c";break;
case 13:
nLetterValue =@"d";break;
case 14:
nLetterValue =@"e";break;
case 15:
nLetterValue =@"f";break;
default:nLetterValue=[[NSString alloc]initWithFormat:@"%lli",ttmpig];
}
str = [nLetterValue stringByAppendingString:str];
if (tmpid == 0) {
break;
}
}
return str;
}
// 十六转二
+ (NSString *)getBinaryByhex:(NSString *)hex
{
NSMutableDictionary *hexDic = [[NSMutableDictionary alloc] init];
hexDic = [[NSMutableDictionary alloc] initWithCapacity:16];
[hexDic setObject:@"0000" forKey:@"0"];
[hexDic setObject:@"0001" forKey:@"1"];
[hexDic setObject:@"0010" forKey:@"2"];
[hexDic setObject:@"0011" forKey:@"3"];
[hexDic setObject:@"0100" forKey:@"4"];
[hexDic setObject:@"0101" forKey:@"5"];
[hexDic setObject:@"0110" forKey:@"6"];
[hexDic setObject:@"0111" forKey:@"7"];
[hexDic setObject:@"1000" forKey:@"8"];
[hexDic setObject:@"1001" forKey:@"9"];
[hexDic setObject:@"1010" forKey:@"A"];
[hexDic setObject:@"1011" forKey:@"B"];
[hexDic setObject:@"1100" forKey:@"C"];
[hexDic setObject:@"1101" forKey:@"D"];
[hexDic setObject:@"1110" forKey:@"E"];
[hexDic setObject:@"1111" forKey:@"F"];
[hexDic setObject:@"1010" forKey:@"a"];
[hexDic setObject:@"1011" forKey:@"b"];
[hexDic setObject:@"1100" forKey:@"c"];
[hexDic setObject:@"1101" forKey:@"d"];
[hexDic setObject:@"1110" forKey:@"e"];
[hexDic setObject:@"1111" forKey:@"f"];
NSMutableString *binaryString=[[NSMutableString alloc] init];
for (int i=0; i<[hex length]; i++) {
NSRange rage;
rage.length = 1;
rage.location = i;
NSString *key = [hex substringWithRange:rage];
//NSLog(@"%@",[NSString stringWithFormat:@"%@",[hexDic objectForKey:key]]);
binaryString = [NSString stringWithFormat:@"%@%@",binaryString,[NSString stringWithFormat:@"%@",[hexDic objectForKey:key]]];
}
// NSLog(@"转化后的二进制为:%@",binaryString);
return binaryString;
}
-------------------------------- 在更新一轮 --------------------------------
正常来说一般情况下蓝牙产品通常不怎么需要我们手动传输信息什么的,我们app通常做好接受工作就可以,但是我们经常要做一些设置操作这就需要我们主动向外发送信息!比如如下两种情况
D13CB362-874F-44E7-8003-13219E78B980.png
45BBB449-8297-4E73-A0FA-85246F8CEEEC.png
第一种还好,关键是第二种很多刚刚开发蓝牙的朋友都绝对会碰到这样的问题如果说没有好的思路这个地方绝对困扰你很久(当时想了半天才想明白怎么搞)当然我的做法不一定是最优的方法暂时就是为大家提供个思路,如果说大家有什么其他好的方法希望能够跟我说一下谢谢
先说说问题是什么样的 :
比如说我开启第一个开关则要发送 : 这条数据二进制 00000001 转换成为 十 进制 为 1 Byte byteData[6] = {176,0,255,3,0,1};
开启第二个开关则要发送 00000010 十进制 为2 发送 Byte byteData[6] = {176,0,255,3,0,2};
........
这个是单个开关开启,如果多个开关同时开启怎么办?
比如说前前三个开关同时开启怎么办,这个要发送00000111 , 转化为十进制则为 7 Byte byteData[6] = {176,0,255,3,0,7};
其实到这为止看着都没什么问题怎么发送指令我们之前都已经说好了,但是这个开关确实有点困难,跟上边不一样不好弄,这八位位数 一共有 256 种组合方式,我们都要把所有的开关都算成一个整体的二进制!初始阶段我们给他一个默认的值.
1 : 定义一个 开关数组
@property (nonatomic,strong)NSMutableArray *setSwitchArray;
2 : 懒加载
-(NSMutableArray *)setSwitchArray{
// 有朋友可能会问了这是什么东西,暂时阶段我们数据存储的是本地
NSString *Path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) lastObject] stringByAppendingString:[NSString stringWithFormat:@"/setSwitchArray.plist"]];
_setSwitchArray = [NSMutableArray arrayWithContentsOfFile:Path];
if (!_setSwitchArray) {
_setSwitchArray = [NSMutableArray arrayWithArray:@[@0,@0,@0,@0,@0,@0,@0,@0]];
[_setSwitchArray writeToFile:Path atomically:YES];
}
return _setSwitchArray;
}
好了到这里发送和接收就基本OK了,这里面的内容基本的蓝牙功能实现基本可以满足了,可能还有有一些其他的问题,我还会后续的慢慢更新,有喜欢的朋友可以给我点个赞,有什么不严谨错误的地方也欢迎大家指正!
注:上方分割线之前的关于蓝牙代理的相关方法选自我的同事<a href = "http://www.jianshu.com/p/1461ebb498ab">[爱吃苹果的兔子 ]