一.硬件测试环境
可将蓝牙模块 通过USB串口模块联接到windows上的串口助手.
这样串口助手可以通过蓝牙模块与手机数据透传通讯.
波特率为115200
(注意安装串口驱动)
在手机未联蓝牙模块之前,可以用助手终端发送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版本。
IOS版本 必须用 Mac OSX 下XCode编译,windows版的Unity 的iOS Build tools只是生成xcode项目文件,最终也是要到mac osx下编译
三.蓝牙插件使用
演示项目中蓝牙插件源码,因为做了很多调整,因此其它项目使用时,可把演示项目里的插件导出成 unity.packet文件后,导入到新项目当中。而不要用直接使用W007 插件。
Android 设置
注意这里最API 数,不能低于 Android 4.3 即SDK 18
包名按最终名称填写,(如果不修改会编译报错)
Android 签名证书调试直接使用Unity 内置即可,正式版还是由开发人员创建正式发布证书
Android 运行
在Android 机器打开USB调试开关,插入Unity 所有机器。
配置好直接点击Build & Run 将会编译apk并直接安装到机器上运行。
如果看不到此按钮,可在Unity 中调整这个Button位置。
以下运行效果
iOS 配置
在developer.apple.com配置
请iOS 开发人员开发帐号里,配置好iOS 开发证书, AppID 以及本App的
Provisioning Profiles文件,下载到Mac OSX ,注册到XCode中。
在Unity 配置
这里配置bundle 最好跟Android package同名。注意在真机上必须开发帐号已经注册的AppID.
最低版本配置为 8.0
选择后直接运行 Build & Run 将会生成Xcode项目文件,在XCode 打开进行下一步配置。(注意每次Build & Run将会重新生成新的XCode文件,覆盖原来文件,注意换成不同目录或及时备份)
在Xcode中配置
每次在Unity 重新Build后,需要做如下几步
第一步:选择开发证书和Provisioning Profiles
如果是调试时自动模式,选择只需选前者
发行版需指明Provisioning Profiles
第二步:配置 info.plist文件
- 增加蓝牙后台通讯模式
App communicates using corebluetooth 和App shares data using corebluetooth - 增加打开蓝牙提示,否则上架补拒
Privacy - Bluetooth Peripheral Usage Description
可以将如下片断直接拷入info.plist 中
NSBluetoothPeripheralUsageDescription
Are you open bluetooth?
UIBackgroundModes
bluetooth-central
bluetooth-peripheral
第三步 增加corebluetooth.framework 框架的链接
第四步 ,增加对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位置。
三.蓝牙硬件知识
本节为通用蓝牙知识,与编 程语言无关,只是为理解接口使用。
蓝牙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
用的简化形式表示。
与模式文档一致,即ffe0下的ffe1是一个双向通道,用于透传
这是另一个工具扫描结果,全格式显示
- 使用完毕,可以断开联接,这样该设备又能重新扫描
四.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) =>
{
});
}
}