Unity 集成蓝牙插件教程

一.硬件测试环境

可将蓝牙模块 通过USB串口模块联接到windows上的串口助手.

这样串口助手可以通过蓝牙模块与手机数据透传通讯.
波特率为115200

(注意安装串口驱动)

Unity 集成蓝牙插件教程_第1张图片
image.png

在手机未联蓝牙模块之前,可以用助手终端发送AT指令进行配置。
如AT+VER 查询模块版本,注意在透传模式下,AT命令后面要带回车有效,才会把数据发送到模块,数据无此要求。

完整测试环境

二.开发环境安装

安装Unity3D/ Android Build tools/iOS Build tools

编译Android 还需安装 Android SDK 和JDK

其中Android BLE在 4.3版本后才支持,所以安装Android SDK 不能低于这个版 本,本次编译版为 Android 4.4 。JDK推荐1.8版本。

Unity 集成蓝牙插件教程_第2张图片
image.png

IOS版本 必须用 Mac OSX 下XCode编译,windows版的Unity 的iOS Build tools只是生成xcode项目文件,最终也是要到mac osx下编译

三.蓝牙插件使用

演示项目中蓝牙插件源码,因为做了很多调整,因此其它项目使用时,可把演示项目里的插件导出成 unity.packet文件后,导入到新项目当中。而不要用直接使用W007 插件。

Android 设置

注意这里最API 数,不能低于 Android 4.3 即SDK 18


Unity 集成蓝牙插件教程_第3张图片
image.png

包名按最终名称填写,(如果不修改会编译报错)

Android 签名证书调试直接使用Unity 内置即可,正式版还是由开发人员创建正式发布证书


Unity 集成蓝牙插件教程_第4张图片
image.png

Android 运行

在Android 机器打开USB调试开关,插入Unity 所有机器。

配置好直接点击Build & Run 将会编译apk并直接安装到机器上运行。
如果看不到此按钮,可在Unity 中调整这个Button位置。

以下运行效果


Unity 集成蓝牙插件教程_第5张图片
image.png

iOS 配置

在developer.apple.com配置

请iOS 开发人员开发帐号里,配置好iOS 开发证书, AppID 以及本App的
Provisioning Profiles文件,下载到Mac OSX ,注册到XCode中。

在Unity 配置

这里配置bundle 最好跟Android package同名。注意在真机上必须开发帐号已经注册的AppID.

最低版本配置为 8.0


Unity 集成蓝牙插件教程_第6张图片
image.png

选择后直接运行 Build & Run 将会生成Xcode项目文件,在XCode 打开进行下一步配置。(注意每次Build & Run将会重新生成新的XCode文件,覆盖原来文件,注意换成不同目录或及时备份)

在Xcode中配置

每次在Unity 重新Build后,需要做如下几步
第一步:选择开发证书和Provisioning Profiles
如果是调试时自动模式,选择只需选前者
发行版需指明Provisioning Profiles


Unity 集成蓝牙插件教程_第7张图片
image.png

第二步:配置 info.plist文件

  • 增加蓝牙后台通讯模式
    App communicates using corebluetooth 和App shares data using corebluetooth
  • 增加打开蓝牙提示,否则上架补拒
    Privacy - Bluetooth Peripheral Usage Description
Unity 集成蓝牙插件教程_第8张图片
image.png

可以将如下片断直接拷入info.plist 中

 NSBluetoothPeripheralUsageDescription
    Are you open bluetooth?
    UIBackgroundModes
    
        bluetooth-central
        bluetooth-peripheral
    

第三步 增加corebluetooth.framework 框架的链接

Unity 集成蓝牙插件教程_第9张图片
image.png

第四步 ,增加对ssl的配置
这里为了防止Unity 内置统计访问ssl 网站报错,在main.mm中主函数第一句加上

setenv("CFNETWORK_DIAGNOSTICS", "3", 1);

这一步可选,主要为处理如下报错

[BoringSSL] Function boringssl_session_errorlog: line 2871 [boringssl_session_read] SSL_ERROR_ZERO_RETURN(6): operation failed because the connection was cleanly shut down with a close_notify alert

Error Domain=CBATTErrorDomain Code=3 "Writing is not permitted." UserInfo={NSLocalizedDescription=Writing is not permitted.}

在Xcode运行

在XCode所在Mac上用USB插入真机设备,点击运行后,点击“检测蓝牙”按钮即开始扫描周边蓝牙设备。如果看不到此按钮,可在Unity 中调整这个Button位置。

image.png

三.蓝牙硬件知识

本节为通用蓝牙知识,与编 程语言无关,只是为理解接口使用。

蓝牙BLE通讯分为主从模式,即一个主模式设备可以同时连接多个从模式设备。主设备标准术语是Central,从设备的标准术语是 peripheral 。在本个案例中,手机为主模式设备,蓝牙为从模式设备。

一个完整的蓝牙通讯有如下几步流程

  1. 主设备(手机)打开蓝牙扫描,把周围从设备的广播收集起来,建立一个设备列表供用户选择,这里广播的术语是 Advertising。

  2. 用户选择了某个设备,相当于选择某蓝牙地址,手机可以用这个蓝牙地址连接从设备。
    注意两点
    2.1.Android下可以从广播中拿到具体蓝牙硬件地址,比如格式 A4:C1:38:77:1A:7A.这样形式,而IOS为了隐私,只提供一组的UUID等效蓝牙硬件地址
    2.2 如果App保存蓝牙地址,可以跳过第一步扫描,直接拿地址用接口去联接设备,这样对用户更友好。

3.联接后,主设备会扫描蓝牙设备提供的功能接口。
每个接口的术语称为service.在一个service下,每个service 用通讯具体的属性,术语称为 characteristic,有翻译成特征字。一个属性可以看成是单向或双向通讯的通道。

write属性可以看主设备向向设备写入通道,notify属性可以主设备从从设备异步通知读入的通道,read属性同步读入通道

如果一个通道有write/notify 即是一个双向通道。

service/characteristic 均是由uuid字符串表示如 “"0000ffe1-0000-1000-8000-00805f9b34fb” 但有一种简写格式4位表示 ffe0它相当于

 string FullUUID(string uuid)
    {
        return "0000" + uuid + "-0000-1000-8000-00805f9b34fb";
    }

即0000ffe0-0000-1000-8000-00805f9b34fb

以下是一个蓝牙工具扫描模块的service/ characteristic
用的简化形式表示。

Unity 集成蓝牙插件教程_第10张图片
image.png

与模式文档一致,即ffe0下的ffe1是一个双向通道,用于透传


Unity 集成蓝牙插件教程_第11张图片
image.png

这是另一个工具扫描结果,全格式显示


Unity 集成蓝牙插件教程_第12张图片
image.png
  1. 使用完毕,可以断开联接,这样该设备又能重新扫描

四.BLE插件调用说明

示例代码集中在BLControl.cs .新的界面可以模仿其中各个写法
所有接口均是对插件封装静态类BluetoothLEHardwareInterface 的调用

0.蓝牙初始化

//参数定义 asCentral 是否是 Central ,手机为true
///参数定义 asPeripheral是否是Peripheral,这里为false
//action 成功回调
//errorAction 失败回调

public static BluetoothDeviceScript Initialize (bool asCentral, bool asPeripheral, Action action, Action errorAction)
它用在系统初始化调用。

实例代码

  BluetoothLEHardwareInterface.Initialize(true, false, () => {
         //初始化成功的回调响应
        }, (error) => {
  //初始化失败的回调响应
            //txt.text = "Initialize" + error;
        });
    

1. 参数配置

根据硬件配置如下参数

 public string DeviceName = "iBlock BLE";
     public string ServiceUUID = "ffe0";
    public string SubscribeCharacteristic = "ffe1";
    public string WriteCharacteristic = "ffe1";

2. 扫描设备

public static void ScanForPeripheralsWithServices (string[] serviceUUIDs, Action action, Action actionAdvertisingInfo = null, bool rssiOnly = false, bool clearPeripheralList = true, int recordType = 0xFF)

扫描所有设备,
serviceUUIDs 指明只扫描带某一类service 的设备,为空表示不限定service
action(address, name) ,不带广播信息扫描设备回调,包含地址,名称
actionAdvertisingInfo 带广播信息的回调,应用于分析广播信息的需求。

实例代码

   BluetoothLEHardwareInterface.ScanForPeripheralsWithServices(null, (address, name) => {
              //扫描处理,加入设备列表              
                AddPeripheral(name, address);

            }, (address, name, rssi, advertisingInfo) => {
                //txt.text = "advertisingInfo" +name ;
                  //扫描处理,加入设备列表
                AddPeripheral(name, address);
            
            });

3. 连接设备

public static void ConnectToPeripheral (string name, Action connectAction, Action serviceAction, Action characteristicAction, Action disconnectAction = null)

name 连接地址/uuid
connectAction 连接成功后回调。
serviceAction() 连接后扫描service的回调,每扫描到一个service,回调执行一次
characteristicAction() 每扫描到一个characteristic执行回调。在本个例子里,执行9次,其中只有ff01 属性对我们通讯有用,只在这次回调中执行。

  BluetoothLEHardwareInterface.ConnectToPeripheral(_deviceAddress, null, null, (address, serviceUUID, characteristicUUID) => {
            
            ServiceUUID = "ffe0";
            //ServiceUUID = serviceUUID;
            //PanelScrollContents.gameObject.SetActive(false);
            PanelPeripherals.gameObject.SetActive(false);

            txt.text = "connect 001 "+ServiceUUID + "="+serviceUUID;
//-----------这里判断是不是我们要找ffe0服务-----------------------------
            if (IsEqual(serviceUUID, ServiceUUID))
            {
                ServiceUUID = serviceUUID;

                txt.text = "States.Connect01 " + serviceUUID+"\n"+ServiceUUID;

                txtId.text += "id "+ serviceUUID + "\n" + _foundSubscribeID + "\n" + characteristicUUID;
                SubscribeCharacteristic = characteristicUUID;
                //WriteCharacteristic = characteristicUUID;
//-----------这里判断是不是我们要找characteristic-----------------------------
                _foundSubscribeID = _foundSubscribeID || IsEqual(characteristicUUID, SubscribeCharacteristic);
               // _foundWriteID = _foundWriteID || IsEqual(characteristicUUID, WriteCharacteristic);

                // if we have found both characteristics that we are waiting for
                // set the state. make sure there is enough timeout that if the
                // device is still enumerating other characteristics it finishes
                // before we try to subscribe




          
                if (_foundSubscribeID )
                {
                    txt.text += "States.Connect03 "+ SubscribeCharacteristic;
//--------------------找到通道,手工打开接收订阅------------------------------
                    SubscribeCharacteristicWithDeviceAddress();
                    _connected = true;
                    TextScanButton.text = "Disconnect";
                    buttonTest.gameObject.SetActive(true);
                    SetState(States.Subscribe, 2f);
                }
            }
        });
    }

4. 发送数据

public static void WriteCharacteristic (string name, string service, string characteristic, byte[] data, int length, bool withResponse, Action action)

name 设备地址/uuid
servcie 发送Characteristic所属service
characteristic 发送characteristic
data[] 发送数据,不超过20byte的字节数组,否则在Android 很多机型上会发送不出去。
withResponse 本通道的写入是否有回调
action(characteristicUUID) 成功回调

 void SendBytes (byte[] data)
    {

      // txt.text = "send byte "+_deviceAddress+"\n"+ServiceUUID+"\n"+WriteCharacteristic;

  
        BluetoothLEHardwareInterface.WriteCharacteristic (_deviceAddress, ServiceUUID, WriteCharacteristic, data, data.Length, false, (characteristicUUID) => {
            
            BluetoothLEHardwareInterface.Log ("Write Succeeded");
        });
    }

注意这里的withResponse 是设置是与通道硬件属性有关,如果通道是write not response ,即Wnr, withResponse = false,如果是write with response 即Wr,则 withResonse = true ,这一属性仅对iOS 版有效,在本例JDK模块里,要选为false

如果实际发送与硬件配置不匹配,iOS 发送失败,并会报错
比如Wnr 通道,withResponse设为true

Error Domain=CBATTErrorDomain Code=3 "Writing is not permitted." UserInfo={NSLocalizedDescription=Writing is not permitted.}

查看iOS代码也能看出来区别来

- (void)writeCharacteristic:(NSString *)name service:(NSString *)serviceString characteristic:(NSString *)characteristicString data:(NSData *)data withResponse:(BOOL)withResponse
{
    if (name != nil && serviceString != nil && characteristicString != nil && _peripherals != nil && data != nil)
    {
        CBPeripheral *peripheral = [_peripherals objectForKey:name];
        if (peripheral != nil)
        {
            CBUUID *cbuuid = [CBUUID UUIDWithString:characteristicString];
            CBCharacteristic *characteristic = [[_peripheralCharacteristics objectForKey:[peripheral name]] objectForKey:cbuuid];
            if (characteristic != nil)
            {
                CBCharacteristicWriteType type = CBCharacteristicWriteWithoutResponse;
                if (withResponse)
                    type = CBCharacteristicWriteWithResponse;
                
                [peripheral writeValue:data forCharacteristic:characteristic type:type];
            }
        }
    }
}

5. 接受数据

这里采用对Notify characteristic 加入接收回调函数处理

public static void SubscribeCharacteristicWithDeviceAddress (string name, string service, string characteristic, Action notificationAction, Action action)

name 设备地址/uuid
servcie 接收Characteristic所属service
characteristic 接收characteristic
notificationAction 接收通知(没有数据)
action(address, characteristicUUID, bytes) bytes就是接收数据

注意这个方法在连接后,才能执行

    void SubscribeCharacteristicWithDeviceAddress() {
            SubscribeCharacteristic = "ffe1";
        if (_connected)
            BluetoothLEHardwareInterface.SubscribeCharacteristicWithDeviceAddress(_deviceAddress, ServiceUUID, SubscribeCharacteristic, (value1, value2) => {
           
                SubscribeCharacteristic = value2;
                
             

            }, (address, characteristicUUID, bytes) =>
            {

        //处理接收数据
                _state = States.None;

                // we received some data from the device
                _dataBytes = bytes;
                //if (_dataBytes.Length > 0)
                {
                    System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();
                    data = UTF8.GetString(_dataBytes);
                }

                txt.text ="recv "+data;
                
     
            });
    }

6.断开连接

UnSubscribeCharacteristic 取消对接收订阅(如果有的话)
DisconnectPeripheral 直正的断开连接方法

 void DisconnectToPeripheral(){
         if (_connected)
        {
            

            BluetoothLEHardwareInterface.UnSubscribeCharacteristic(_deviceAddress, ServiceUUID, SubscribeCharacteristic, null);
            BluetoothLEHardwareInterface.DisconnectPeripheral(_deviceAddress, (address) =>
            {
                
            });
        }
    }

你可能感兴趣的:(Unity 集成蓝牙插件教程)