前段时间接了一个项目有关蓝牙的,但是自己之前没怎么接触过蓝牙,就再网上各种search相关的文章,但是感觉都不是很具体,现在贴出来自己做的项目蓝牙模块实现过程,希望和大家共同学习,其实并没有多难,如果你在做蓝牙这一块的话,我建议你下载一个App---LightBlue。
1.需求
主要是做一个教育类App,连接蓝牙进行数据传输,并且对蓝牙硬件进行数据读取和写入。并对蓝牙进行一对一的绑定,我做的这个蓝牙硬件一共分为两部分,一部分为蓝牙主体(内置蓝牙硬件,以下简称教棒),还有一部分就是一个IC卡片(作用相当于一个名片,我理解起来其实就是一个拥有独立id的一个东西)。
2.实现功能
App连接并绑定指定的蓝牙设备教棒,对教棒进行指令发送和应答。其实蓝牙设备收发数据通过的是指令,和网络请求差不多:
第一步:当然要连接到蓝牙,App开启蓝牙并寻找蓝牙信号,截获到蓝牙设备的广播信号(前提是蓝牙要开启状态),判断蓝牙的名字或者别的是不是自己要连接的蓝牙硬件,如果是直接连接。
第二步:收发数据
---App发给蓝牙一个指令(一般为十六进制的data,实现方式就是写数据到蓝牙),该指令代表我要获取你的系统地址:"嗨,蓝牙把你的地址发给我"。
---蓝牙收到指令后智能判断指令的含义(这部分一般蓝牙硬件厂商会给出一个蓝牙协议文档,来表明各个口令和具体代表的含义),会反馈一个信息给App:我收到你的指令了, 然后蓝牙会继续返回App要的数据:“噢~~~,你想要我的系统地址是吧,拿去***这就是我的系统地址,你可以用这个来作为我的唯一标识符”。
---App实现相应的代码来接收该数据。收到数据后一般要给蓝牙一个回复,有的硬件没做这一块:(嗨,蓝牙我收到你的数据了,ps:这怎么跟TCP三次握手差不多啊)和网络请求一样收到得数据一般都是data类型,App端要进行数据解析,并作进一步处理:存储或者上传到服务器进行账号蓝牙绑定等。
---大体流程就是这样,我写的比较简单明了。
建立中心角色—扫描外设(discover)—连接外设(connect)—扫描外设中的服务和特征(discover)—与外设做数据交互(explore and interact)—断开连接(disconnect)。
3.具体实现细节
(1)建立中心角色(这里可以是手机也可以是蓝牙硬件,我是手机作为中心角色进行扫描蓝牙信号的)。首先在我自己类的头文件中要包含CoreBluetooth的头文件,并继承两个协议
创建一个CBCentralManager成员变量作为中心
@property(nonatomic,retain)CBCentralManager * manager;
并在viewDidLoad中实例化
self.manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
App首先要判断手机的蓝牙是否开启
这里是Did代理函数是自动执行的
//蓝牙状态改变
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
NSString * message;
switch (central.state) {
case 0:
message = @"初始化中,请稍后……";
break;
case 1:
message = @"设备不支持状态,过会请重试……";
break;
case 2:
message = @"设备未授权状态,过会请重试……";
break;
case 3:
message = @"设备未授权状态,过会请重试……";
break;
case 4:
message = @"尚未打开蓝牙,请在设置中打开……";
break;
case 5:
message = @"蓝牙已经成功开启,稍后……";
break;
default:
break;
}
if (_manager.state != CBCentralManagerStatePoweredOn ) {//如果没有开启提示是否开启
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"开启蓝牙" message:nil delegate:self cancelButtonTitle:@"不开启" otherButtonTitles:@"开启", nil];
alertView.tag = OPENBLUETOOTH;
[alertView show];
}else{
//如果已经手机开启了蓝牙,那么便扫描蓝牙硬件
[self.manager scanForPeripheralsWithServices:nil options:nil];
}
}
#pragma mark - alertViewDelegate
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{
if (alertView.tag == OPENBLUETOOTH) {
if (buttonIndex == 1) {
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"prefs:root=Bluetooth"]];
}
}else if (alertView.tag == ISBINDALERT){
[self.navigationController popViewControllerAnimated:YES];
}else if (alertView.tag == RESCANALERT){
if (buttonIndex == 1) {
[self.manager scanForPeripheralsWithServices:nil options:nil];
[self initTimerAndTimeCount];
}else{
[self.navigationController popViewControllerAnimated:YES];
}
}
}
//手机蓝牙发现了一个蓝牙硬件peripheral//每发现一个蓝牙设备都会调用此函数(如果想展示搜索到得蓝牙可以逐一保存peripheral并展示)
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI{
NSLog(@"发现蓝牙设备:%@",peripheral.name);//
if ([peripheral.name isEqual:蓝牙的名字]) {
self.peripheral = peripheral;
[self.manager connectPeripheral:self.peripheral options:nil];//如果是自己要连接的蓝牙硬件,那么进行连接
}
}
//返回的蓝牙服务通知通过代理实现
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error{
for (CBService * service in peripheral.services) {
NSLog(@"Service found with UUID :%@",service.UUID);
// if ([service.UUID isEqual:[CBUUID UUIDWithString:@"18F0"]]) {
[peripheral discoverCharacteristics:nil forService:service];
// }
}
}
//查找到该设备所对应的服务
- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error{
//每个peripheral都有很多服务service(这个依据蓝牙而定),每个服务都会有几个特征characteristic,区分这些就是UUID
//这里可以利用开头说的LightBlue软件连接蓝牙看看你的蓝牙硬件有什么服务和每个服务所包含的特征,然后根据你的协议里面看看你需要用到哪个特征的哪个服务
for (CBCharacteristic * characteristic in service.characteristics) {
// NSLog(@"查找到的服务(属性)%@",characteristic);
//所对应的属性用于接收和发送数据
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF0"]]) {
[peripheral setNotifyValue:YES forCharacteristic:characteristic];//监听这个服务发来的数据
[peripheral readValueForCharacteristic:characteristic];//主动去读取这个服务发来的数据
}
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF1"]]) {
_characteristic = characteristic;
//*****此处已经连接好蓝牙,可以在这里给蓝牙发指令,也就是写入数据
// [self sendMessageWithType:_type];//1.查询数量
例:
NSMutableData *value = [NSMutableData data];
在这里把数据转成data存储到value里面
NSLog(@"%@",value);
[_peripheral writeValue:value forCharacteristic:_characteristic type:CBCharacteristicWriteWithResponse];
}
}
}
//接收数据的函数.处理蓝牙发过来得数据 读数据代理,这里已经收到了蓝牙发来的数据
-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
if (error) {
NSLog(@"Error discovering characteristics: %@", [error localizedDescription]);
return;
}
if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"2AF0"]]) {
NSLog(@"收到蓝牙发来的数据%@",characteristic.value);
NSString * string = [self hexadecimalString:characteristic.value];
//在这里解析收到的数据,一般是data类型的数据,这里要根据蓝牙厂商提供的协议进行解析并且配合LightBlue来查看数据结构,我当时收到的数据是十六进制的数据但是是data类型,所以我先讲data解析出来之后转为十进制来使用。具体方法后面我会贴出
//还有一点收到数据后有的硬件是需要应答的,如果应答的话就是在这里再给蓝牙发一个指令(写数据):“我收到发的东西了,你那边要做什么操作可以做了”。
}
}
//*****写数据代理,上面写入数据之后就会自动调用这个函数
- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
NSLog(@"%@",characteristic.UUID);
if (error) {
NSLog(@"Error changing notification state: %@",[error localizedDescription]);
}
//其实这里貌似不用些什么(我是没有写只是判断了连接状态)
}
如果要重复读写数据,可以在每次收到数据之后发送读取的指令,我做的项目就是一条一条的收数据:发一个读取指令,返回数据,App做出应答(其实就是再发一个应答的指令),解析之后再发一条读取指令,蓝牙收到指令后删除这个数据,如此反复,直到蓝牙没有数据了。停止发送。
之前我有收到我做的项目还有一个IC卡,蓝牙可以对IC进行读取,并生成特殊数据保存到蓝牙内存中,这个跟蓝牙连接并没有什么太大关联,我的项目中用到,但我觉得大多数设备应该不会用到这,就没有详细讲。
4.我用到的相关解析函数
//将传入的NSData类型转换成NSString并返回
- (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++){
[hexString appendString:[NSString stringWithFormat:@"%02lx", (unsigned long)dataBuffer[i]]];
}
result = [NSString stringWithString:hexString];
return result;
}
//将传入的NSString类型转换成NSData并返回
- (NSData*)dataWithHexstring:(NSString *)hexstring{
NSData* aData;
return aData = [hexstring dataUsingEncoding: NSUTF16StringEncoding];
}
// 十六进制转换为普通字符串的。
- (NSString *)stringFromHexString:(NSString *)hexString { //
char *myBuffer = (char *)malloc((int)[hexString length] / 2 + 1);
bzero(myBuffer, [hexString length] / 2 + 1);
for (int i = 0; i < [hexString length] - 1; i += 2) {
unsigned int anInt;
NSString * hexCharStr = [hexString substringWithRange:NSMakeRange(i, 2)];
NSScanner * scanner = [[NSScanner alloc] initWithString:hexCharStr];
[scanner scanHexInt:&anInt];
myBuffer[i / 2] = (char)anInt;
}
NSString *unicodeString = [NSString stringWithCString:myBuffer encoding:4];
NSLog(@"------字符串=======%@",unicodeString);
return unicodeString;
}
//转换成十进制
- (NSString *)to10:(NSString *)num
{
NSString *result = [NSString stringWithFormat:@"%ld", strtoul([num UTF8String],0,16)];
return result;
}
//转换成十六进制
- (NSString *)to16:(int)num
{
NSString *result = [NSString stringWithFormat:@"%@",[[NSString alloc] initWithFormat:@"%1x",num]];
if ([result length] < 2) {
result = [NSString stringWithFormat:@"0%@", result];
}
return result;
}
5.总结
当时我写这个项目的时候各种查资料,Apple官方的demo也被我下载下来研究,好几天不知道怎么搞,也没有仔细看厂商给的蓝牙协议文档,这里提醒大家,一定要好好研究厂商给的蓝牙协议文档,因为好多东西都在那上面,没有那个绝对做不出来!第一次写技术分享,写的不好,欢迎指正,希望可以帮到一些Developer。