BLE低功耗蓝牙与经典蓝牙(持续更新)

一、蓝牙类别与简介

  • 蓝牙分单模和双模
    单模 (BLE / LE):早年商标叫 Bluetooth Smart,只能干低功耗的活(手环、智能家居)
    双模(BR/EDR + LE):早年商标叫 Bluetooth Smart Ready,现在的手机、电脑全是这种
    经典蓝牙(BR/EDR):常简称 BT,专门负责听歌、打电话

  • BLE 与经典蓝牙是两套独立的协议栈,单模 BLE 设备无法直接访问经典蓝牙服务。若需同时使用心率监测(BLE)和蓝牙耳机(BT),必须选用双模(BR/EDR + LE)蓝牙芯片,是否兼容取决于硬件模组,而非协议互斥。

  • 在连接能力上,经典蓝牙主设备最多同时连接 7 个从设备;而 BLE 的连接数并非协议固定值,而是由控制器缓存和芯片设计决定,常见手机支持 3~8 个,高端芯片可更多。

  • 传输能力差异明显:经典蓝牙基于 L2CAP,默认 MTU 至少 672 字节且支持动态协商,协议栈自动完成分片与重组,应用层无需手动分包;BLE 默认 ATT MTU 为 23 字节(有效载荷 20 字节),需手动分包或通过 requestMtu()协商扩容更大 MTU。

  • 功耗与应用场景各有侧重。BLE 为蓝牙 4.0 引入的低功耗协议,一颗纽扣电池可支撑数月甚至数年,广泛用于智能家居、穿戴设备等 IoT 场景;经典蓝牙则更适合语音、音乐等大数据量传输。

  • 经典蓝牙模块可细分为:2004 年推出的传统蓝牙(蓝牙 2.1,分 Class1/2/3,覆盖约 100m/10m/1m)和 2009 年推出的高速蓝牙(速率约 24Mbps,约为传统的八倍)。Android 从 4.3 起提供原生 BLE API,8.0 起原生蓝牙堆栈已基本支持蓝牙 5.0 特性,开发者可直接通过系统接口与 BLE 设备交互。

差异对比

二、低功耗蓝牙开发

开发步骤如下:

1.GATT协议介绍:

GATT 协议结构层级关系: Device (一个设备) -> Service (多个服务) -> Characteristic (多个特征) -> Descriptor (N个描述符)和一个Value属性。

UUID: 每个层级包括每个Descriptor都有唯一的 UUID 标识,类似数据库主键。

蓝牙的读写都是通过characteristic属性来进行的。

GATT协议

BLE信息

先获取BluetoothAdapter实例,然后扫描,再获取蓝牙驱动Device,然后连接蓝牙驱动,监听连接回调,获取一个驱动Device下的所有不同功能的service数组,通过service的uuid获取需要的service,拿到service后通过特征的uuid获取需要的特征Characteristic。通过操作特征Characteristic的Value可以对目标蓝牙读取和写入数据命令。

//1. Android 4.3以上,Android 5.0以下
mBluetoothAdapter.startLeScan(BluetoothAdapter.LeScanCallback LeScanCallback)
//2. Android 5.0以上,扫描的结果在mScanCallback中进行处理
mBluetoothLeScanner=mBluetoothAdapter.getBluetoothLeScanner(); 
mBluetoothLeScanner.startScan(ScanCallback mScanCallback);
2.profile(数据配置文件)

一个profile文件可以包含一个或者多个Service,一个profile文件包含需要的服务的信息或者为对等设备如何交互的配置文件的选项信息。ProfileProfile 并不是实际存在于 BLE 外设上的,它只是一个被 Bluetooth SIG 或者外设设计者预先定义的 Service 的集合。

BLE技术是基于GATT进行连接与通信的,GATT是一种属性传输协议,简单的讲可以认为是一种属性传输的应用层协议。结构图如下

image

3.UUID:GATT 协议中,每一个层级都绑定了一个 16 进制的 UUID 标识。UUID 是全球唯一的 128bit 标识符,1 个十六进制数字代表 4 个 bit,用来识别不同的特性,作用类似 Socket 通信中的端口号。

蓝牙核心规范定义了一个 Base UUID(128 位),所有蓝牙技术联盟(Bluetooth SIG)定义的属性都共用这个底座:

0x0000xxxx-0000-1000-8000-00805F9B34FB

为了简化表示,每一个蓝牙技术联盟定义的属性都有一个唯一的 16 位短 UUID,用来替换上面 Base UUID 中的 xxxx 部分 。例如,心率测量特性的短 UUID 为 0x2A37,拼接后完整的 128 位 UUID 为:

0x00002A37-0000-1000-8000-00805F9B34FB

虽然蓝牙技术联盟共用同一个 Base UUID,但 16 位短 UUID 足以唯一识别联盟所定义的各种属性。需要注意的是,Base UUID 不能用于任何自定义的属性、服务和特性,自定义内容必须使用完整的 128 位随机 UUID。

谨记:每一个Service、属性特征characteristic、属性描述Descriptor都有一个专属的UUID做为标识。

4.主从设备

Center(中心设备,主动发起连接的一方,如手机)

Center主要可以分为 扫描->连接->通讯 三块内容. 通讯主要包含read,write,notify/indicate。read就是读取设备上的数据(如读取外设电量),write就是发送数据(如发送关闭命令关闭蓝牙灯),notify/indicate 字面意思就是通知/指示, 是用来接收设备主动上报的数据的(例如手环可以每隔1秒就告诉APP心率值, 然后APP展现一个心率谱图),这样就达成了双向通讯。

Peripheral(外设设备,被动等待连接的一方,如手环)

Peripheral主要理解为硬件外设,提供数据用的。在开发APP时很少关心这个,因为大家都是直接拿着硬件来调试的。在android5.0时,增加了Peripheral相关的API, 意味着可以让android设备模拟成外设,作为Peripheral来提供数据。这样,当我们没有硬件设备的时候,可以拿2台手机进行BLE开发,很方便。还可以扩展很多其他功能,比如使用BLE实现蓝牙聊天(google sample里面有经典蓝牙的聊天demo),还可以把手机模拟成iBeacon等等。

5、数据发送接收
BLE蓝牙Cline端,最多一次性发送20byte长度的数据,所以往往我们开发中是不够用的,这时候可以采用分包发送。

  • 设置MTU,MTU(Maximum Transmission Unit) 是 BLE(蓝牙低功耗)通信中单次数据传输的最大字节数。默认 MTU 为 23 字节(实际有效载荷20 字节)。对于需要传输较大数据的场景,需根据 Android 版本选择不同策略:Android5.0及以上可以自定义最大传输单元,Android5.0以下无法设置。所以Android5.0以下机型上传输数据大于20个字节,必须分包传输。MTU设置建议在BLE设备连接成功时进行设置mBluetoothGatt.requestMtu(requiredMtu)

  • 接收和发送数据的格式多种多样,有ASCII码 、十六进制、字符串,甚至还有Json。只要能读取到数据就没什么问题。characteristic.setValue( )值数据类型可以是byte[]、String、int

  • 数据过大的解决方案如下:具体的核心理论就是分包,也就是把一条命令分成多次来发送,而后最后组装一下即可。

    分包发送

    如上图所示,模拟命令分成15个包传输过来,每个包大小固定位最大20个字符,第1-14包为模拟命令数据,第十五个包只发送了一个EOM字符串,接收端每接收到一个包则把这个数据追加到上一个包数据上,直到收到EOM标识也就是一个包的结束标识,则把当前收到的所有数据进行解析,解析完成就可以进行下一步业务处理了。
    还可以与硬件设备协商其他分包协议格式,如每个包“FramedId+包序号+总包数+信息内容”方式实现分包数据拼接与校验。FramedId (帧标识)完全是根据业务需求自定义设计的应用层协议字段。

分包

ble读和写:

蓝牙BLE(BlueTooth BLE)入门及爬坑指南
BLE传输大数据

——>分包发送代码示例:

private void writeData(String hex){
        //1.通过UUID获取Service服务
        BluetoothGattService service=mBluetoothGatt.getService(write_UUID_service);
       //2.通过UUID获取Characteristic特征
        BluetoothGattCharacteristic charaWrite=service.getCharacteristic(write_UUID_chara);
        byte[] data=HexUtil.hexStringToBytes(hex);
        if (data.length>20){
            //数据大于20个字节 分批次写入
            //总包数
            int num=0;
            if (data.length%20!=0){
                num=data.length/20+1;
            }else{
                num=data.length/20;
            }
            //分包发送
            for (int i=0;i<num;i++){
                byte[] tempArr;
                if (i==num-1){
                    //最后一包
                    tempArr=new byte[data.length-i*20];
                }else{
                    //此前n包
                    tempArr=new byte[20];
                }
                //3.通过对Characteristic的Value进行读写
                charaWrite.setValue(tempArr);
                mBluetoothGatt.writeCharacteristic(charaWrite);
            }
        }else{
              //单包,总信息不足20字节
             //3.通过对Characteristic的Value进行读写
            charaWrite.setValue(data);
            mBluetoothGatt.writeCharacteristic(charaWrite);
        }
    }

——>开启订阅,接收信息

//订阅通知
    mBluetoothGatt.setCharacteristicNotification(mBluetoothGatt.getService(notify_UUID_service).getCharacteristic(notify_UUID_chara),true);

注意在写入之前要先开启订阅,要不然就收不到写入的数据,我一般都是在发现服务之后就订阅。关于订阅收不到这里,需要注意一下,首先你写入的和订阅的Characteristic对象一定要属于同一个Service对象,另外就是保证你写入的数据没问题,否则就可能收不到订阅回调。

三、经典蓝牙开发

Android-经典蓝牙(BT)-建立长连接传输短消息和文件
1、经典蓝牙的开发类似一个Socket连接。Clinet端需要创建一个BluetoothSocket,服务端需要创建一个BluetoothServerSocket监听待连接的uuid的clinet,这个监听uuid类似Socket的端口监听。数据操作采用InputStream输入流与 OutputStream输出流进行读写。

SPP_UUID是定义的一个UUID,在连接中类似Scoket的端口作用。

——Clinet端——

//加密传输,Android系统强制配对,弹窗显示配对码
//BluetoothSocket socketC = device.createRfcommSocketToServiceRecord(SPP_UUID); 

 //明文传输(不安全),无需配对
BluetoothSocket socketC = device.createInsecureRfcommSocketToServiceRecord(SPP_UUID);

socketC.connect();

//服务端和客户端都可获取输入流与输出流,然后读取数据与发送数据信息
DataOutputStream out = new DataOutputStream(socketC.getOutputStream());
DataInputStream in = new DataInputStream(socketC.getInputStream());

——Service端——

BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
//加密传输,Android强制执行配对,弹窗显示配对码
//BluetoothServerSocket mSSocket = adapter.listenUsingRfcommWithServiceRecord(TAG, SPP_UUID); 

//明文传输(不安全),无需配对
BluetoothServerSocket mSSocket = adapter.listenUsingInsecureRfcommWithServiceRecord(TAG, SPP_UUID); 

// 监听连接
BluetoothSocket socketSer = mSSocket.accept(); 

//发送信息用输出流
DataOutputStream mOut = new DataOutputStream(socketSer.getOutputStream());
//读取信息用输入流
DataInputStream mIn= new DataInputStream(socketSer.getInputStream());

// 关闭监听,只连接一个设备
mSSocket.close(); 

GitHub上不错的项目推荐
1.一款非常棒的经典蓝牙与BLE含Service+Clinet端demo
2.FastBle一款非常棒的BLE框架
3.Android-BluetoothKit蓝牙框架

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容