iOS蓝牙浅析

作者: Smallwolf_JS | 来源:发表于2015-11-02 16:37 被阅读3031次

    前段时间接了一个项目有关蓝牙的,但是自己之前没怎么接触过蓝牙,就再网上各种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。

    相关文章

      网友评论

      • 睿少:您好,请问iOS怎么通过 mac 连接蓝牙设备吗?
        睿少:@Smallwolf_JS 不是, mac相当于一个唯一标志,我通过这个 mac 值用手机连接蓝牙设备
        Smallwolf_JS:你是说用mac连接蓝牙硬件是吗?原理是一样的啊,你是要开发mac的软件么
      • 12345上山打老虎丶:大神 请教个问题 广播里面有mac地址 但是这个地址是这样的<ffffeecc 89bc4f66 85002a01 22> 怎么解析成 正常的Mac地址 ?
        12345上山打老虎丶:@Smallwolf_JS 大神 690571757 你加下我 麻烦了
        12345上山打老虎丶:@Smallwolf_JS 转出来后 是这个“ffffc536b650a1f68500290117” 这个不是mac地址啊
        https://blog.csdn.net/dkq972958298/article/details/53321429 我用这个转的 大神看看 这个问题纠结半天了
        Smallwolf_JS:@12345上山打老虎丶 你把这个转换成十六进制看看是不是你要的mac地址无拼接的状态,目前十六进制你强转下
      • Woody_芜笛:请问下:手机与蓝牙建立连接,手机未与蓝牙连接状态下,蓝牙名字为nill;有连接过的状态下,蓝牙名字才出现 要求:手机首次检索蓝牙名称能够显示。你们会出现这种情况吗?
        Smallwolf_JS:@Woody永 你可以咨询一下你的蓝牙硬件的厂商,看看他们的硬件设备的名称是什么,这个是出场的时候就设定好的。
        Woody_芜笛:是个对象的一个属性,但是拿出来的值是未nill,在系统蓝牙内,获取到nil的值是不会在系统蓝牙内显示的
        Smallwolf_JS:@Woody永 为连接的情况下,获取到的蓝牙名字应该是个数组,你的nil状况应该是获取错了
      • lemon007:您好,我现在用的这个蓝牙模块,厂商没有提供专门的外设说明书和文档之类的东西,说是通用的,没做自定义和封装,这个通用的我要去哪里找?
        Smallwolf_JS:@lemon007 你们的蓝牙模块是要做什么呢,实现什么功能呢,需不需要通信,还是链接上就可以呢?
        lemon007:@Smallwolf_JS 没有具体的协议,公司在淘宝买的蓝牙模块,然后厂商那边有提供一个他们公司的资料下载网站,上面没找到相关资料。他们说就是通用的通信协议。
        Smallwolf_JS:但是蓝牙协议你们得有啊,没有协议,你们怎么跟你们得蓝牙通讯呢?
      • 南方小金豆:楼主,你这样检测蓝牙会不会弹两次提示啊?我在centralManagerDidUpdateState这里面没有弹提示,系统自己给我弹了一个,有办法去掉这个嘛?
        Smallwolf_JS:@那份牵挂给了谁 如果你没有打开蓝牙,系统会提示你打开蓝牙,这个提示是没办法去掉的,所以你就没有必要自己去弹提示了。
      • csong:这个应该是调用BLE的API吧
        Smallwolf_JS:苹果有相关的蓝牙函数
      • 33a02bf71691:你好,请问可以获取到的手机蓝牙地址吗?
        Smallwolf_JS:@重复昵称 你看看你的蓝牙协议应该有吧,没有的话看看lightblue会显示的
        33a02bf71691:@Smallwolf_JS 该怎么获取呢 :no_mouth:
        Smallwolf_JS:@重复昵称 可以的
      • 给自己定个小目标:你好,我的蓝牙已经可以打开,想请教你个问题,可以加下扣扣号码吗?1198115229 朋仔
      • 3fb1e463a533:你好,我的蓝牙已经连上,可以请教一个问题么?在切换页面的时候想让蓝牙也可以正常使用该什么样的思路实现呢?能不能像传参数或者定义成全局变量一样给别的页面使用呢? :pensive:
        Smallwolf_JS:@vsz009 可以啊,只要你不断开蓝牙,让参数不被销毁就可以
      • H_Liuju:你好!我做的模块没有厂商给的蓝牙协议文档,但是有软件的小Demo,然而经过了各种数据变换之后我已经不知道原来返回的数据是哪样了,如何是好?像一个个去试也不知道该如何找到全部的特征UUID :joy:
        H_Liuju:@Smallwolf_JS 好的,谢谢,我再试试看 :grin:
        Smallwolf_JS:@H_Liuju 上面的我的文章有说,常见没有给协议文档这就很难办了(你可以问厂家要啊),要是没有的话可能你的蓝牙硬件就是普通功能,没有特别的,一般的UUID像电量什么的大部分都是一样的。给你Demo你就找找对应的服务和特征。

      本文标题:iOS蓝牙浅析

      本文链接:https://www.haomeiwen.com/subject/acjqhttx.html