iOS蓝牙浅析

前段时间接了一个项目有关蓝牙的,但是自己之前没怎么接触过蓝牙,就再网上各种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的头文件,并继承两个协议<CBCentralManagerDelegate,CBPeripheralDelegate>,代码如下:

创建一个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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容