Unity 集成蓝牙插件教程
一.硬件测试环境
可将蓝牙模块 通过USB串口模块联接到windows上的串口助手.
这样串口助手可以通过蓝牙模块与手机数据透传通讯.
波特率为115200
(注意安装串口驱动)
![](https://img.haomeiwen.com/i1493747/7dbe1bc7e03115a7.png)
在手机未联蓝牙模块之前,可以用助手终端发送AT指令进行配置。
如AT+VER 查询模块版本,注意在透传模式下,AT命令后面要带回车有效,才会把数据发送到模块,数据无此要求。
![](https://img.haomeiwen.com/i1493747/d815c63f70728a22.png)
二.开发环境安装
安装Unity3D/ Android Build tools/iOS Build tools
编译Android 还需安装 Android SDK 和JDK
其中Android BLE在 4.3版本后才支持,所以安装Android SDK 不能低于这个版 本,本次编译版为 Android 4.4 。JDK推荐1.8版本。
![](https://img.haomeiwen.com/i1493747/c92f5a66fb7779cd.png)
IOS版本 必须用 Mac OSX 下XCode编译,windows版的Unity 的iOS Build tools只是生成xcode项目文件,最终也是要到mac osx下编译
三.蓝牙插件使用
演示项目中蓝牙插件源码,因为做了很多调整,因此其它项目使用时,可把演示项目里的插件导出成 unity.packet文件后,导入到新项目当中。而不要用直接使用W007 插件。
Android 设置
注意这里最API 数,不能低于 Android 4.3 即SDK 18
![](https://img.haomeiwen.com/i1493747/d2c84d91ad66a1d5.png)
包名按最终名称填写,(如果不修改会编译报错)
Android 签名证书调试直接使用Unity 内置即可,正式版还是由开发人员创建正式发布证书
![](https://img.haomeiwen.com/i1493747/cb742a9bea3db927.png)
Android 运行
在Android 机器打开USB调试开关,插入Unity 所有机器。
配置好直接点击Build & Run 将会编译apk并直接安装到机器上运行。
如果看不到此按钮,可在Unity 中调整这个Button位置。
以下运行效果
![](https://img.haomeiwen.com/i1493747/7d7bff226cd3fcb4.png)
iOS 配置
在developer.apple.com配置
请iOS 开发人员开发帐号里,配置好iOS 开发证书, AppID 以及本App的
Provisioning Profiles文件,下载到Mac OSX ,注册到XCode中。
在Unity 配置
这里配置bundle 最好跟Android package同名。注意在真机上必须开发帐号已经注册的AppID.
最低版本配置为 8.0
![](https://img.haomeiwen.com/i1493747/51a5b3aa4afc6961.png)
选择后直接运行 Build & Run 将会生成Xcode项目文件,在XCode 打开进行下一步配置。(注意每次Build & Run将会重新生成新的XCode文件,覆盖原来文件,注意换成不同目录或及时备份)
在Xcode中配置
每次在Unity 重新Build后,需要做如下几步
第一步:选择开发证书和Provisioning Profiles
如果是调试时自动模式,选择只需选前者
发行版需指明Provisioning Profiles
![](https://img.haomeiwen.com/i1493747/0bf31ca46d92bbd5.png)
第二步:配置 info.plist文件
- 增加蓝牙后台通讯模式
App communicates using corebluetooth 和App shares data using corebluetooth - 增加打开蓝牙提示,否则上架补拒
Privacy - Bluetooth Peripheral Usage Description
![](https://img.haomeiwen.com/i1493747/330176fb6492070f.png)
可以将如下片断直接拷入info.plist 中
<key>NSBluetoothPeripheralUsageDescription</key>
<string>Are you open bluetooth?</string>
<key>UIBackgroundModes</key>
<array>
<string>bluetooth-central</string>
<string>bluetooth-peripheral</string>
</array>
第三步 增加corebluetooth.framework 框架的链接
![](https://img.haomeiwen.com/i1493747/3de3669bb7510786.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位置。
![](https://img.haomeiwen.com/i1493747/0bbcc295c5b4d0c2.png)
三.蓝牙硬件知识
本节为通用蓝牙知识,与编 程语言无关,只是为理解接口使用。
蓝牙BLE通讯分为主从模式,即一个主模式设备可以同时连接多个从模式设备。主设备标准术语是Central,从设备的标准术语是 peripheral 。在本个案例中,手机为主模式设备,蓝牙为从模式设备。
一个完整的蓝牙通讯有如下几步流程
-
主设备(手机)打开蓝牙扫描,把周围从设备的广播收集起来,建立一个设备列表供用户选择,这里广播的术语是 Advertising。
-
用户选择了某个设备,相当于选择某蓝牙地址,手机可以用这个蓝牙地址连接从设备。
注意两点
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
用的简化形式表示。
![](https://img.haomeiwen.com/i1493747/a3f9acc665b8ff2e.png)
与模式文档一致,即ffe0下的ffe1是一个双向通道,用于透传
![](https://img.haomeiwen.com/i1493747/fb5b98ed3b66e8d2.png)
这是另一个工具扫描结果,全格式显示
![](https://img.haomeiwen.com/i1493747/a16bb41722ca02c6.png)
- 使用完毕,可以断开联接,这样该设备又能重新扫描
四.BLE插件调用说明
示例代码集中在BLControl.cs .新的界面可以模仿其中各个写法
所有接口均是对插件封装静态类BluetoothLEHardwareInterface 的调用
0.蓝牙初始化
//参数定义 asCentral 是否是 Central ,手机为true
///参数定义 asPeripheral是否是Peripheral,这里为false
//action 成功回调
//errorAction 失败回调
public static BluetoothDeviceScript Initialize (bool asCentral, bool asPeripheral, Action action, Action<string> 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<string, string> action, Action<string, string, int, byte[]> 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<string> connectAction, Action<string, string> serviceAction, Action<string, string, string> characteristicAction, Action<string> 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<string> 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<string, string> notificationAction, Action<string, string, byte[]> 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) =>
{
});
}
}