​ 在蓝牙开发中,有些情况是不需要连接的,只要外设广播自己的数据即可,例如苹果的ibeacon。自Android 5.0更新蓝牙API后,手机可以作为外设广播数据。

广播包有两种:

广播包(Advertising Data)

响应包(Scan Response)

其中广播包是每个外设都必须广播的,而响应包是可选的。每个广播包的长度必须是31个字节,如果不到31个字节 ,则剩下的全用0填充 补全,这部分的数据是无效的

image

广播数据单元

广播包中包含若干个广播数据单元,广播数据单元也称为 AD Structure。

广播数据单元 = 长度值Length + AD type + AD Data。

长度值Length只占一个字节,并且位于广播数据单元的第一个字节。

概念的东西有些抽象,先看看下面的广播报文:

image

​ 0x代表这串字符串是十六进制的字符串。两位十六进制数代表一个字节。因为两个字符组成的十六进制字符串最大为FF,即255,而Java中byte类型的取值范围是-128到127,刚好可以表示一个255的大小。所以两个十六进制的字符串表示一个字节。

​ 继续查看报文内容,开始读取第一个广播数据单元。读取第一个字节:0x07,转换为十进制就是7,即表示后面的7个字节是这个广播数据单元的数据内容。超过这7个字节的数据内容后,表示是一个新的广播数据单元。

​ 而第二个广播数据单元,第一个字节的值是0x16,转换为十进制就是22,表示后面22个字节为第二个广播数据单元。

​ 在广播数据单元的数据部分中,第一个字节代表数据类型(AD type),决定数据部分表示的是什么数据。(即广播数据单元第二个字节为AD type)

image

AD Type的类型如下:

Flags:TYPE = 0x01。用来标识设备LE物理连接。

bit 0: LE 有限发现模式

bit 1: LE 普通发现模式

bit 2: 不支持 BR/EDR

bit 3: 对 Same Device Capable(Controller) 同时支持 BLE 和 BR/EDR

bit 4: 对 Same Device Capable(Host) 同时支持 BLE 和 BR/EDR

bit 5..7: 预留

​ 这bit 1~7分别代表着发送该广播的蓝牙芯片的物理连接状态。当bit的值为1时,表示支持该功能。

例:

image

Service UUID。广播数据中可以将设备支持的GATT Service的UUID广播出来,来告知中心设备其支持的Service。对于不同bit的UUID,其对应的类型也有不同:

非完整的16bit UUID: TYPE = 0x02;

完整的16bit UUID 列表: TYPE = 0x03;

非完整的32bit UUID 列表: TYPE = 0x04;

完整的32bit UUID 列表: TYPE = 0x05;

非完整的128bit UUID 列表: TYPE = 0x06;

完整的128bit UUID: TYPE = 0x07;

TX Power Level: TYPE = 0x0A,表示设备发送广播包的信号强度。 数值范围:±127 dBm。

设备名字,DATA 是名字的字符串,可以是设备的全名,也可以是设备名字的缩写。

缩写的设备名称: TYPE = 0x08

完整的设备名称: TYPE = 0x09

Service Data: Service 对应的数据。

16 bit UUID Service: TYPE = 0x16, 前 2 字节是 UUID,后面是 Service 的数据;

32 bit UUID Service: TYPE = 0x20, 前 4 字节是 UUID,后面是 Service 的数据;

128 bit UUID Service: TYPE = 0x21, 前 16 字节是 UUID,后面是 Service 的数据;

厂商自定义数据: TYPE = 0xFF。厂商数据中,前两个字节表示厂商ID,剩下的是厂商自定义的数据。

BLE广播

蓝牙广播的数据格式大致讲了一下,有助于下面的广播操作的理解。

自定义UUID:

//`UUID`

public static `UUID` UUID_SERVICE = `UUID`.fromString("0000fff7-0000-1000-8000-00805f9b34fb");

开启广播一般需要3~4对象:广播设置(AdvertiseSettings)、广播包(AdvertiseData)、扫描包(可选)、广播回调(AdvertiseCallback)。

广播设置

先看看广播设置(AdvertiseSettings)如何定义:

//初始化广播设置

mAdvertiseSettings = new AdvertiseSettings.Builder()

//设置广播模式,以控制广播的功率和延迟。

.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER)

//发射功率级别

.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)

//不得超过180000毫秒。值为0将禁用时间限制。

.setTimeout(3000)

//设置是否可以连接

.setConnectable(false)

.build();

(1)、通过AdvertiseSettings.Builder#setAdvertiseMode() 设置广播模式。其中有3种模式:

在均衡电源模式下执行蓝牙LE广播:AdvertiseSettings#ADVERTISE_MODE_BALANCED

在低延迟,高功率模式下执行蓝牙LE广播: AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY

在低功耗模式下执行蓝牙LE广播:AdvertiseSettings#ADVERTISE_MODE_LOW_POWER

(2)、通过AdvertiseSettings.Builder#setAdvertiseMode() 设置广播发射功率。共有4种功率模式:

使用高TX功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_HIGH

使用低TX功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_LOW

使用中等TX功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM

使用最低传输(TX)功率级别进行广播:AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW

(3)、通过AdvertiseSettings.Builder#setTimeout()设置持续广播的时间,单位为毫秒。最多180000毫秒。当值为0则无时间限制,持续广播,除非调用BluetoothLeAdvertiser#stopAdvertising()停止广播。

(4)、通过AdvertiseSettings.Builder#setConnectable()设置该广播是否可以连接的。

广播包

之前说过,外设必须广播广播包,扫描包是可选。但添加扫描包也意味着广播更多得数据,即可广播62个字节。

//初始化广播包

mAdvertiseData = new AdvertiseData.Builder()

//设置广播设备名称

.setIncludeDeviceName(true)

//设置发射功率级别

.setIncludeDeviceName(true)

.build();

//初始化扫描响应包

mScanResponseData = new AdvertiseData.Builder()

//隐藏广播设备名称

.setIncludeDeviceName(false)

//隐藏发射功率级别

.setIncludeDeviceName(false)

//设置广播的服务`UUID`

.addService`UUID`(new Parcel`UUID`(UUID_SERVICE))

//设置厂商数据

.addManufacturerData(0x11,hexStrToByte(mData))

.build();

可见无论是广播包还是扫描包,其广播的内容都是用AdvertiseData类封装的。

(1)、AdvertiseData.Builder#setIncludeDeviceName()方法,可以设置广播包中是否包含蓝牙的名称。

(2)、AdvertiseData.Builder#setIncludeTxPowerLevel()方法,可以设置广播包中是否包含蓝牙的发射功率。

(3)、AdvertiseData.Builder#addServiceUUID(ParcelUUID)方法,可以设置特定的UUID在广播包中。

(4)、AdvertiseData.Builder#addServiceData(ParcelUUID,byte[])方法,可以设置特定的UUID和其数据在广播包中。

(5)、AdvertiseData.Builder#addManufacturerData(int,byte[])方法,可以设置特定厂商Id和其数据在广播包中。

​ 从AdvertiseData.Builder的设置中可以看出,如果一个外设需要在不连接的情况下对外广播数据,其数据可以存储在UUID对应的数据中,也可以存储在厂商数据中。但由于厂商ID是需要由Bluetooth SIG进行分配的,厂商间一般都将数据设置在厂商数据。

广播名称

另外可以通过BluetoothAdapter#setName()设置广播的名称

//获取蓝牙设配器

BluetoothManager bluetoothManager = (BluetoothManager)

getSystemService(Context.BLUETOOTH_SERVICE);

mBluetoothAdapter = bluetoothManager.getAdapter();

//设置设备蓝牙名称

mBluetoothAdapter.setName("daqi");

发送BLE广播

先看一个例子,我们分别在广播包和扫描包中设置AdvertiseData.Builder的每一种广播报文参数,得到一下报文内容:

image

(1)、Type = 0x01 表示设备LE物理连接。

(2)、Type = 0x09 表示设备的全名

(3)、Type = 0x03 表示完整的16bit UUID。其值为0xFFF7。

(4)、Type = 0xFF 表示厂商数据。前两个字节表示厂商ID,即厂商ID为0x11。后面的为厂商数据,具体由用户自行定义。

(5)、Type = 0x16 表示16 bit UUID的数据,所以前两个字节为UUID,即UUID为0xF117,后续为UUID对应的数据,具体由用户自行定义。

最后继承AdvertiseCallback自定义广播回调。

private class daqiAdvertiseCallback extends AdvertiseCallback {

//开启广播成功回调

@Override

public void onStartSuccess(AdvertiseSettings settingsInEffect){

super.onStartSuccess(settingsInEffect);

Log.d("daqi","开启服务成功");

}

//无法启动广播回调。

@Override

public void onStartFailure(int errorCode) {

super.onStartFailure(errorCode);

Log.d("daqi","开启服务失败,失败码 = " + errorCode);

}

}

初始化完毕上面的对象后,就可以进行广播:

//获取BLE广播的操作对象。

//如果蓝牙关闭或此设备不支持蓝牙LE广播,则返回null。

mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();

//mBluetoothLeAdvertiser不为空,且蓝牙已开打

if(mBluetoothAdapter.isEnabled()){

if (mBluetoothLeAdvertiser != null){

//开启广播

mBluetoothLeAdvertiser.startAdvertising(mAdvertiseSettings,

mAdvertiseData, mScanResponseData, mAdvertiseCallback);

}else {

Log.d("daqi","该手机不支持ble广播");

}

}else{

Log.d("daqi","手机蓝牙未开启");

}

​ 广播主要是通过BluetoothLeAdvertiser#startAdvertising()方法实现,但在之前需要先获取BluetoothLeAdvertiser对象。

BluetoothLeAdvertiser对象存在两个情况获取为Null:

手机蓝牙模块不支持BLE广播

蓝牙未开启

所以在调用BluetoothAdapter#getBluetoothLeAdvertiser()前,需要先调用判断蓝牙已开启,并判断在BluetoothAdapter中获取的BluetoothLeAdvertiser是否为空(测试过某些华为手机mBluetoothAdapter.isMultipleAdvertisementSupported()为 false, 但是能发送ble广播)。

​ 与广播成对出现就是BluetoothLeAdvertiser.stopAdvertising()停止广播了,传入开启广播时传递的广播回调对象,即可关闭广播:

mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback)

启动GATT Service 和 Characteristic

​ 虽然通过广播告知外边自身拥有这些Service,但手机自身并没有初始化Gattd的Service。导致外部的中心设备连接手机后,并不能找到对应的GATT Service 和 获取对应的数据。

创建Gatt Service

Service类型有两个级别:

BluetoothGattService#SERVICE_TYPE_PRIMARY 主服务

BluetoothGattService#SERVICE_TYPE_SECONDARY次要服务(存在于主服务中的服务)

创建BluetoothGattService时,传入两个参数:UUID和Service类型:

BluetoothGattService service = new BluetoothGattService(UUID_SERVICE,

BluetoothGattService.SERVICE_TYPE_PRIMARY);

创建Gatt Characteristic

​ 我们都知道Gatt中,Service的下一级是Characteristic,Characteristic是最小的通信单元,通过对Characteristic进行读写操作来进行通信。

//初始化特征值

mGattCharacteristic = new BluetoothGattCharacteristic(UUID_CHARACTERISTIC,

BluetoothGattCharacteristic.PROPERTY_WRITE|

BluetoothGattCharacteristic.PROPERTY_NOTIFY|

BluetoothGattCharacteristic.PROPERTY_READ,

BluetoothGattCharacteristic.PERMISSION_WRITE|

BluetoothGattCharacteristic.PERMISSION_READ);

创建BluetoothGattCharacteristic时,传入三个参数:UUID、特征属性 和 权限属性。

​ 特征属性表示该BluetoothGattCharacteristic拥有什么功能,即能对BluetoothGattCharacteristic进行什么操作。其中主要有3种:

BluetoothGattCharacteristic#PROPERTY_WRITE 表示特征支持写

BluetoothGattCharacteristic#PROPERTY_READ 表示特征支持读

BluetoothGattCharacteristic#PROPERTY_NOTIFY 表示特征支持通知

权限属性用于配置该特征值所具有的功能。主要两种:

BluetoothGattCharacteristic#PERMISSION_WRITE 特征写权限

BluetoothGattCharacteristic#PERMISSION_READ 特征读权限

注意事项

当特征值只有读权限时,调用BluetoothGatt#writeCharacteristic()对特征值进行修改时,将返回false,无法写入。并不会触发BluetoothGattCallback#onCharacteristicWrite()回调。

当特征值只有写权限时,调用BluetoothGatt#readCharacteristic()对特征值进行读取时,将返回false,无法写入。并不会触发BluetoothGattCallback#onCharacteristicRead()回调。

创建Gatt Descriptor

Characteristic下还有Descriptor,初始化BluetoothGattDescriptor时传入:Descriptor UUID 和 权限属性

//初始化描述

mGattDescriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,BluetoothGattDescriptor.PERMISSION_WRITE);

添加 Characteristic 和 Descriptor

为Service添加Characteristic,为Characteristic添加Descriptor:

//Service添加特征值

mGattService.addCharacteristic(mGattCharacteristic);

mGattService.addCharacteristic(mGattReadCharacteristic);

//特征值添加描述

mGattCharacteristic.addDescriptor(mGattDescriptor);

​ 通过蓝牙管理器mBluetoothManager获取Gatt Server,用来添加Gatt Service。添加完Gatt Service后,外部中心设备连接手机时,将能获取到对应的GATT Service 和 获取对应的数据

//初始化GattServer回调

mBluetoothGattServerCallback = new daqiBluetoothGattServerCallback();

if (mBluetoothManager != null)

mBluetoothGattServer = mBluetoothManager.openGattServer(this, mBluetoothGattServerCallback);

boolean result = mBluetoothGattServer.addService(mGattService);

if (result){

Toast.makeText(daqiActivity.this,"添加服务成功",Toast.LENGTH_SHORT).show();

}else {

Toast.makeText(daqiActivity.this,"添加服务失败",Toast.LENGTH_SHORT).show();

}

​ 定义Gatt Server回调。当中心设备连接该手机外设、修改特征值、读取特征值等情况时,会得到相应情况的回调。

private class daqiBluetoothGattServerCallback extends BluetoothGattServerCallback{

//设备连接/断开连接回调

@Override

public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {

super.onConnectionStateChange(device, status, newState);

}

//添加本地服务回调

@Override

public void onServiceAdded(int status, BluetoothGattService service) {

super.onServiceAdded(status, service);

}

//特征值读取回调

@Override

public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {

super.onCharacteristicReadRequest(device, requestId, offset, characteristic);

}

//特征值写入回调

@Override

public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {

super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);

}

//描述读取回调

@Override

public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {

super.onDescriptorReadRequest(device, requestId, offset, descriptor);

}

//描述写入回调

@Override

public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {

super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);

}

}

最后开启广播后,用nRF连接后看到的特征值信息如下图所示:(加多了一个只能都的特征值)

image

android BLE系列:

android 蓝牙 广播,android蓝牙BLE(三) —— 广播相关推荐

  1. android 蓝牙相关广播,Android 蓝牙相关的广播

    Android 蓝牙相关的广播 监听蓝牙相关的广播并获得相关的信息,蓝牙相关的广播主要集中在BluetoothAdapter和BluetoothDevice类中, 可以通过在AndroidManife ...

  2. android 蓝牙相关广播,Android通过广播接收器BroadcastReceiver监听蓝牙连接变化

    上一篇介绍了通过广播对蓝牙的打开关闭等变化的监听,这一篇将对蓝牙连接状态的变化进行封装. 首先是广播接收器BlueToothConnectReceiver 类 import android.bluet ...

  3. Android 蓝牙4.0(BLE)开发实现对蓝牙的写入数据和读取数据

    由于最近学校组织了一个移动APP(安卓)设计大赛,自己也学习安卓有一段时间了,就跟同学商量一起去参加试试,一拍即合,然后我们就开始想idea,因为最近可穿戴设备比较火,我们也就想试试.经过商量,我负责 ...

  4. Android蓝牙开发—经典蓝牙和BLE(低功耗)蓝牙的区别

    找到一篇介紹BT与BLE使用差别的文章, 写的很清晰,看完基本明白了 ----------------------------------------------------------------- ...

  5. Android蓝牙开发 — 经典蓝牙BLE蓝牙

    一,前期基础知识储备 1)蓝牙是一种支持设备之间短距离通信的无线电技术(其他还包括红外,WIFI): 支持移动电话.笔记本电脑.无线耳机等设备之间进行信息的交换: Android支持的蓝牙协议栈:Bl ...

  6. 蓝牙ble自定义广播内容()

    目录 一.蓝牙广播数据包定义与释义 二.解析广播数据 2.1.获取本地蓝牙名称 2.2.获取16位数据包 2.2.1.将字节转换为hex 2.3.32位于128位字节解析 一.蓝牙广播数据包定义与释义 ...

  7. BLE广播流程介绍 蓝牙广播 低功耗蓝牙广播的实现流流程介绍 /BLE Advertising flow ----- 蓝牙低功耗协议栈

    零. 概述 主要介绍下蓝牙协议栈(bluetooth stack)低功耗蓝牙广播的流程以及协议栈的实现流程,BLE  Advertising flow btsnoop以及流程在资料中的......\S ...

  8. 蓝牙解析(part5):BLE的广播通信

    转自Wowo大神的http://www.wowotech.net/bluetooth/ble_broadcast.html 1. 前言 大家都知道,相比传统蓝牙,蓝牙低功耗(BLE)最大的突破就是加大 ...

  9. android 连接蓝牙电子秤_电子秤蓝牙双模通讯Android低功耗蓝牙(蓝牙4.0)BLE开发(上)...

    电子秤蓝牙双模通讯Android低功耗蓝牙(蓝牙4.0)BLE开发(上) 前段时间,公司项目用到了手机APP和蓝牙设备的通讯开发,这里也正好对低功耗蓝牙(蓝牙4.0及以后标准)的开发,做一个总结. 蓝 ...

最新文章

  1. BIEE-CSS样式大全
  2. qt tounicode 崩溃_吐槽大会丨细数那些打块的崩溃瞬间!看看你中了几枪?
  3. OpenStack 的部署T版(一)——基础环境配置
  4. (一)梳理前端知识体系,搞定大厂必考面试题
  5. 密码机 密钥管理项目安装配置 从零开始
  6. 量子计算机创新,中国式量子霸权:光子量子计算机研究创新纪-24小时-虎嗅网...
  7. mysql数据库做关联查询_mysql 数据库join关联查询using(xxx)的作用
  8. 计算机更新安装失败,电脑更新安装失败
  9. python3下载url图片假死_利用Python 向FTP 上传图片,程序假死?
  10. 写java代码的软件_新手编写java代码使用什么软件
  11. 硬件芯片----74HC595芯片的运用原理
  12. oa软件测试用例,OA系统测试用例.doc
  13. 微博数据:如何在西瓜微数平台使用「热门话题分析」功能?
  14. FastDFS和GFS以及NFS的对比
  15. linux如何连接手机传文件,Ubuntu和手机通过蓝牙互传文件
  16. Java8 Stream流中的 collect() 方法,远比你想象中的强大
  17. RedHat7.0 实验指南之firewall的应用
  18. 信息安全原理与实践(第2版) [Mark Stamp 著][张戈 译] PDF完整版
  19. 机械臂速成小指南(三):机械臂的机械结构
  20. 【机器学习笔记】:大话线性回归(二)拟合优度和假设检验

热门文章

  1. 【游戏交互设计】古登堡图:视觉流在设计中的应用
  2. 中国商用5纳米工艺,技术领先优势获得认可,美国AMD给出5年合同
  3. eclipse更新项目中的代码,必须手动clean才能清除
  4. 利用requests+re+bs4 爬取微星官网部分产品信息(主要是一些笔记本,少量台式机)
  5. 编写一个菜单驱动程序的雏形。该程序显示一个提供4个选项的菜单——每个选项用一个字母标记。如果用户使用有效选项之外的字母进行响应,程序将提示用户输入一个有效的字母,直到用户这样做为止。
  6. 用800行代码做个行为树(Behavior Tree)的库(3)
  7. js 隔行变色+鼠标移上换色+鼠标离开颜色恢复+鼠标点击弹出点击的是第几行是什么颜色
  8. 三国杀服务器维护时间,[OL][公告] 《三国杀OL》8月17日停服维护公告
  9. Maxthon阻止了一个错误,...Flash9e.ocx解决办法
  10. Delphi--最强大的开发工具(欢迎转载)