• IOS 蓝牙(GameKit、Core Bluetooth)


    GameKit的蓝牙开发注意
    只能用于iOS设备之间的连接
    只能用于同一个应用程序之间的连接
    最好别利用蓝牙发送比较大的数据

      /*
     关于蓝牙的数据传输
     
     1. 一次性传送,没有中间方法,所谓中间方法值得是,传输进度比例
     对于用户而言,选择了传输,就需要等待传输完成,或者传输以失败告终
     这就意味着,在实际开发过程中,最好不要用蓝牙传输太大的文件
     
     在实际应用中,蓝牙通常用于传递游戏数据模型,用于联机对战,譬如点对点的棋牌类游戏。
     */

     
    iOS中蓝牙的实现方案
    iOS中提供了4个框架用于实现蓝牙连接
    GameKit.framework(用法简单)
    只能用于iOS设备之间的连接,多用于游戏(比如五子棋对战),iOS7开始过期

     

    MultipeerConnectivity.framework
    只能用于iOS设备之间的连接,iOS7开始引入,主要用于文件共享(仅限于沙盒的文

    件)

    ExternalAccessory.framework
    可用于第三方蓝牙设备交互,但是蓝牙设备必须经过苹果MFi认证(国内较少)

    CoreBluetooth.framework(时下热门)
    可用于第三方蓝牙设备交互,必须要支持蓝牙4.0
    硬件至少是4s,系统至少是iOS6
    蓝牙4.0以低功耗著称,一般也叫BLE(Bluetooth Low Energy)

    目前应用比较多的案例:运动手坏、嵌入式设备、智能家居

    GameKit的蓝牙开发步骤
    显示可以连接的蓝牙设备列表
    GKPeerPickerController *ppc = [[GKPeerPickerController alloc] init]; ppc.delegate = self;
    [ppc show];

     

    在代理方法中监控蓝牙的连接
    - (void)peerPickerController:(GKPeerPickerController *)picker

    didConnectPeer:(NSString *)peerID toSession:(GKSession *)session { NSLog(@"连接到设备:%@", peerID);
    // 关闭蓝牙设备显示界面
    [picker dismiss];

    // 设置接收到蓝牙数据后的监听器
    [session setDataReceiveHandler:self withContext:nil]; // 保存session
    self.session = session;

    }

     

    处理接收到的蓝牙数据
    - (void) receiveData:(NSData *)data fromPeer:(NSString *)peer

     

    inSession: (GKSession *)session context:(void *)context {
    }
    

    利用GKSession给其他设备发送数据 给指定的连接设备发送数据

    - (BOOL)sendData:(NSData *) data toPeers:(NSArray *)peers
    withDataMode:(GKSendDataMode)mode error:(NSError **)error;
    

    给所有连接的设备发送数据
    - (BOOL)sendDataToAllPeers:(NSData *) data withDataMode:

    (GKSendDataMode)mode error:(NSError **)error;
    

    实例1:

    #import "ViewController.h"
    #include <GameKit/GameKit.h>
    
    @interface ViewController ()<UINavigationControllerDelegate, UIImagePickerControllerDelegate, GKPeerPickerControllerDelegate>
    /**
     *  连接
     */
    - (IBAction)connect;
    /**
     *  选择图片
     */
    - (IBAction)selectedPhoto;
    /**
     *  发送
     */
    - (IBAction)send;
    
    @property (weak, nonatomic) IBOutlet UIImageView *customIV;
    /**
     *  会话
     */
    @property (nonatomic, strong) GKSession *session;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    
    - (IBAction)connect {
        
        // 1.创建选择其他蓝牙设备的控制器
        GKPeerPickerController *peerPk = [[GKPeerPickerController alloc] init];
        // 2.成为该控制器的代理
        peerPk.delegate = self;
        // 3.显示蓝牙控制器
        [peerPk show];
    }
    #pragma mark - GKPeerPickerControllerDelegate
    // 4.实现dialing方法
    /**
     *  当蓝牙设备连接成功就会调用
     *
     *  @param picker  触发时间的控制器
     *  @param peerID  连接蓝牙设备的ID
     *  @param session 连接蓝牙的会话(可用通讯), 以后只要拿到session就可以传输数据
     */
    - (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session
    {
        NSLog(@"%@", peerID);
        // 1.保存会话
        self.session = session;
        
        // 2.设置监听接收传递过来的数据
        /*
         Handler: 谁来处理接收到得数据
         withContext: 传递数据
         */
        [self.session setDataReceiveHandler:self withContext:nil];
        
        
        // 2.关闭显示蓝牙设备控制器
        [picker dismiss];
    }
    /**
     *  接收到其它设备传递过来的数据就会调用
     *
     *  @param data    传递过来的数据
     *  @param peer    传递数据设备的ID
     *  @param session 会话
     *  @param context 注册监听时传递的数据
     */
    - (void) receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context
    {
    //    NSLog(@"%s", __func__);
        // 1.将传递过来的数据转换为图片(注意: 因为发送的时图片, 所以才需要转换为图片)
        UIImage *image = [UIImage imageWithData:data];
        self.customIV.image = image;
    }
    
    
    - (void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker
    {
        
    }
    
    
    - (IBAction)send {
        // 利用session发送图片数据即可
        // 1.取出customImageView上得图片, 转换为二进制
        UIImage *image =  self.customIV.image;
        NSData *data = UIImagePNGRepresentation(image);
        
        /*
         GKSendDataReliable, 数据安全的发送模式, 慢
         GKSendDataUnreliable, 数据不安全的发送模式, 快
         */
        
        /*
         data: 需要发送的数据
         DataReliable: 是否安全的发送数据(发送数据的模式)
         error: 是否监听发送错误
         */
        [self.session sendDataToAllPeers:data withDataMode:GKSendDataReliable error:nil];
    }
    
    
    - (IBAction)selectedPhoto
    {
        
        // 1.创建图片选择控制器
        UIImagePickerController *imagePk = [[UIImagePickerController alloc] init];
        // 2.判断图库是否可用打开
        if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum])
        {
            // 3.设置打开图库的类型
            imagePk.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
            
            imagePk.delegate = self;
            
            // 4.打开图片选择控制器
            [self presentViewController:imagePk animated:YES completion:nil];
        }
    }
    #pragma mark - UIImagePickerControllerDelegate
    - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
    {
    //    NSLog(@"%@", info);
        self.customIV.image = info[UIImagePickerControllerOriginalImage];
        
        [picker dismissViewControllerAnimated:YES completion:nil];
    }
    
    @end
    View Code

     实例2:

    /*
     > 讲解相片选择器
     *先不设置代理,创建后直接运行
     *从选择相片后需要关闭控制器引出代理
     *实现代理方法展示数据
     *PPT讲解蓝牙连接(不是所有应用都可以用蓝牙传输东西,必须相同应用事先写好如何处理. 苹果蓝牙传输非常满, 而且传输过程中没有进度)
     *在storyboard中添加按钮, 点击按钮后创建对端选择控制器, 设置代理,显示视图控制器
     *在代理方法中打印peerID, 讲解session用途查看头文件引出利用传递数据
     *定义属性保存session , 在storyboard中添加按钮监听按钮点击利用session传递图片
     *讲解传递数据方法两种模式区别
     *设置数据处理者, 讲解如何找到数据处理方法,在数据处理方法中打印LOG运行验证
     *将接收到的数据转换为图片后现实在界面上
     *总结蓝牙传输
     */
    
    #import "CZViewController.h"
    #import <GameKit/GameKit.h>
    
    @interface CZViewController ()<UIImagePickerControllerDelegate, UINavigationControllerDelegate, GKPeerPickerControllerDelegate>
    
    /**
     * 现实相片
     */
    - (IBAction)selectPhoto;
    
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    
    /**
     * 蓝牙连接
     */
    - (IBAction)connectBtnClick;
    
    /**
     * 会话对象
     */
    @property (nonatomic, strong)GKSession *session;
    
    /**
     * 发送相片
     */
    - (IBAction)sendPhoto;
    
    @end
    
    @implementation CZViewController
    
    #pragma mark 选择照片
    /*
     照片源类型
     
     UIImagePickerControllerSourceTypeCamera            照相机
     UIImagePickerControllerSourceTypePhotoLibrary      照片库(通过同步存放的,用户不能删除)
     UIImagePickerControllerSourceTypeSavedPhotosAlbum  保存的照片(通过拍照或者截屏保存的,用户可以删除)
     */
    - (IBAction)selectPhoto
    {
        
        
        // 1.判断照片源是否可用
        if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
            // 1.1实例化控制器
            UIImagePickerController *picker = [[UIImagePickerController alloc] init];
            // 1.2设置照片源
            [picker setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
            // 1.3设置允许修改
            picker.allowsEditing = YES;
            // 1.4设置代理
            picker.delegate = self;
            // 1.5显示控制器
            [self presentViewController:picker animated:YES completion:^{
                
            }];
        }else
        {
            // 2.照片源不可用
            NSLog(@"照片源不可用");
        }
    }
    
    #pragma mark - imagePicker代理方法
    - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
    {
    //    NSLog(@"%@", info);
        // 获取相片
        UIImage *image = info[@"UIImagePickerControllerEditedImage"];
        // 设置相片
        self.imageView.image = image;
        
        // 关闭相片选择器
        [self dismissViewControllerAnimated:YES completion:^{
            
        }];
        
        
    }
    
    #pragma mark - 蓝牙连接
    - (IBAction)connectBtnClick
    {
        // 1.创建对端选择控制器
        GKPeerPickerController *picker = [[GKPeerPickerController alloc] init];
        
        // 2.设置代理
        picker.delegate = self;
        
        // 3.显示试图控制器
        [picker show];
        
    }
    
    #pragma mark - GKPeerPickerControllerDelegate
    // 完成对端连接
    // GKSession对象用于表现两个蓝牙设备之间连接的一个会话,你也可以使用它在两个设备之间发送和接收数据。
    - (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession:(GKSession *)session
    {
        NSLog(@"连接成功 %@", peerID);
        // 保存会话
        self.session = session;
        
         // 设置数据处理对象,类似于delegate
        [self.session setDataReceiveHandler:self withContext:nil];
        
        // 关闭对端选择控制器
        [picker dismiss];
    }
    
    // 取消对端选择控制器
    - (void)peerPickerControllerDidCancel:(GKPeerPickerController *)picker
    {
        NSLog(@"取消蓝牙选择器");
    }
    
    // 发送相片
    - (IBAction)sendPhoto {
        
        NSData *imageData = UIImagePNGRepresentation(self.imageView.image);
        
        // 利用session发送相片
    //    self.session sendData:<#(NSData *)#> toPeers:<#(NSArray *)#> withDataMode:<#(GKSendDataMode)#> error:<#(NSError *__autoreleasing *)#>
        /*
         TCP协议、UDP协议
         
         1. 要发送的数据(二进制的)
         2. 数据发送模式
         GKSendDataReliable      :确保数据发送成功(TCP协议,对网络压力大)
         GKSendDataUnReliable    :只管发送不管成功(UDP协议,对数据完整性要求不高,对网络压力下)
         */
        [self.session sendDataToAllPeers:imageData withDataMode:GKSendDataReliable error:nil];
        
    }
    
    
    // 数据接受处理方法,此方法需要从文档中粘贴出来,没有智能提示
    - (void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession: (GKSession *)session context:(void *)context
    {
        NSLog(@"接收到数据");
         // 将NSData转换成UIImage
        UIImage *image = [UIImage imageWithData:data];
        self.imageView.image = image;
    }
    
    /*
     关于蓝牙的数据传输
     
     1. 一次性传送,没有中间方法,所谓中间方法值得是,传输进度比例
     对于用户而言,选择了传输,就需要等待传输完成,或者传输以失败告终
     这就意味着,在实际开发过程中,最好不要用蓝牙传输太大的文件
     
     在实际应用中,蓝牙通常用于传递游戏数据模型,用于联机对战,譬如点对点的棋牌类游戏。
     */
    
    @end
    View Code
    Core Bluetooth
    ● Core Bluetooth测试比较麻烦,正常情况下,得至少有2台真实的蓝牙4.0设备
    ● 如何让iOS模拟器也能测试蓝牙4.0程序?
    ● 买一个CSR蓝牙4.0 USB适配器,插在Mac上
    ● 在终端输入sudo nvram bluetoothHostControllerSwitchBehavior="never"
    ● 重启Mac
    ● 用Xcode 4.6调试代码,将程序跑在iOS 6.1的模拟器上 (苹果把iOS 7.0模拟器对BLE的支持移除掉了)
    ● Core Bluetooth的使用场景
    ● 运动手环、智能家居、嵌入式设备等等(金融刷卡器、心电测量器)
    Core Bluetooth的基本常识
    ● 每个蓝牙4.0设备都是通过服务(Service)和特征(Characteristic)来展示自己 的
    ● 一个设备必然包含一个或多个服务,每个服务下面又包含若干个特征 ● 特征是与外界交互的最小单位
    • 比如说,一台蓝牙4.0设备,用特征A来描述自己的出厂信息,用特征B来收发 数据
     ● 服务和特征都是用UUID来唯一标识的,通过UUID就能区别不同的服务和特征 ● 设备里面各个服务(service)和特征(characteristic)的功能,均由蓝牙设备硬件厂
    商提供,比如哪些是用来交互(读写),哪些可获取模块信息(只读)等
    Core Bluetooth的开发步骤
    ● 建立中心设备
    ● 扫描外设(Discover Peripheral)
    ● 连接外设(Connect Peripheral)
    ● 扫描外设中的服务和特征(Discover Services And Characteristics)
    ● 利用特征与外设做数据交互(Explore And Interact)
    ● 断开连接(Disconnect)
    实例:
    #import "ViewController.h"
    #import <CoreBluetooth/CoreBluetooth.h>
    
    @interface ViewController ()<CBCentralManagerDelegate, CBPeripheralDelegate>
    /**
     *  外设
     */
    @property (nonatomic, strong) NSMutableArray *peripherals;
    /**
     *  中心管理者
     */
    @property (nonatomic, strong) CBCentralManager *mgr;
    @end
    
    @implementation ViewController
    
    - (NSMutableArray *)peripherals
    {
        if (!_peripherals) {
            _peripherals = [NSMutableArray array];
        }
        return _peripherals;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        // 1.创建中心设备
        CBCentralManager *mgr = [[CBCentralManager alloc] init];
        self.mgr = mgr;
        
        
        // 设置代理
        mgr.delegate = self;
        
        // 2.利用中心设备扫描外部设备
        /*
         如果指定数组代表只扫描指定的设备
         */
        [mgr scanForPeripheralsWithServices:nil options:nil];
    }
    #pragma mark - CBCentralManagerDelegate
    - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
    {
    
        // 保存扫描到得外部设备
        // 判断如果数组中不包含当前扫描到得外部设置才保存
        if (![self.peripherals containsObject:peripheral]) {
            
            peripheral.delegate = self;
            [self.peripherals addObject:peripheral];
        }
    }
    
    /**
     *  模拟点击, 然后连接所有的外设
     */
    - (void)start
    {
        for (CBPeripheral *peripheral in self.peripherals) {
            /**
             *  连接外设
             */
            [self.mgr connectPeripheral:peripheral options:nil];
        }
    }
    /**
     *  连接外设成功调用
     */
    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
    {
        // 扫描外设中得服务
        [peripheral discoverServices:nil];
    }
    /**
     *  连接外设失败调用
     */
    - (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
    {
        
    }
    
    #pragma makr - CBPeripheralDelegate
    /**
     *  只要扫描到服务就会调用
     *
     *  @param peripheral 服务所在的外设
     */
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error
    {
        
        // 获取外设中所有扫描到得服务
        NSArray *services = peripheral.services;
        for (CBService *service in services) {
            // 拿到需要的服务
            if ([service.UUID.UUIDString isEqualToString:@"123"])
            {
                // 从需要的服务中查找需要的特征
                // 从peripheral中得service中扫描特征
                [peripheral discoverCharacteristics:nil forService:service];
            }
        }
    }
    
    /**
     *  只要扫描到特征就会调用
     *
     *  @param peripheral 特征所属的外设
     *  @param service    特征所属的服务
     */
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error
    {
        
        // 拿到服务中所有的特诊
        NSArray *characteristics =  service.characteristics;
        // 遍历特征, 拿到需要的特征处理
        for (CBCharacteristic * characteristic in characteristics) {
            if ([characteristic.UUID.UUIDString isEqualToString:@"8888"]) {
                NSLog(@"设置闹钟");
    
            }
        }
    }
    @end
    View Code
    蓝牙的现状
    ● 绝大多数智能手机支持蓝牙 4.0(BLE)
    ● 蓝牙芯片发展迅速,在性能和效率方面都有很大提高,且不断变得更小更便宜
    ● iBeacon + 蓝牙,前景一片光明
    ● 应用之一:室内导航
    ● Estimote公司为iBeacon提供基站
    ● 3个iBeacon基站的预购价格为99美元(约合人民币610元)
    ● Estimote公司推出的iBeacon基站的最远传输距离为50m,但是他们推荐在10m 范围内的使用效果最好
    ● 一块纽扣电池就能为一个iBeacon基站提供长达 2 年的使用寿命,而且是在设 备不断对外发射信号的情况下
  • 相关阅读:
    闭包
    作用域
    既然踏足前端,便要立志成为专家
    D3引擎用正则运算的方式,实现智能设备APP消息推送
    基于ArduinoUNOR3的智能调速风扇
    【一起来玩RTOS系列】之RT-Thread Nano快速创建工程
    MCU代码自动生成工具,全面升级
    ESP8266 SOC门磁系统(一)---短信报警功能
    正点原子F407/103,接入机智云,点亮LED
    机智云5.0推出IoT套件GoKit4.0 可实现物联网应用协同开发
  • 原文地址:https://www.cnblogs.com/liuwj/p/6870123.html
Copyright © 2020-2023  润新知