低功耗蓝牙BLE外围模式(peripheral)-使用BLE作为服务端

Android对外模模式(peripheral)的支持

从Android5.0开始才支持

关键术语和概念

以下是关键BLE术语和概念的摘要:

  • 通用属性简档(GATT) - GATT简档是用于通过BLE链路发送和接收称为“属性”的短数据块的一般规范。 所有当前的低能量应用配置文件都基于GATT。

    蓝牙SIG为低能量设备定义了许多配置文件 。 配置文件是设备在特定应用程序中的工作方式的规范。 请注意,设备可以实现多个配置文件。 例如,设备可以包含心率监视器和电池水平检测器。
  • 属性协议(ATT) -GATT建立在属性协议(ATT)之上。 这也称为GATT / ATT。 ATT经过优化,可在BLE设备上运行。 为此,它使用尽可能少的字节。 每个属性由通用唯一标识符(UUID)唯一标识,UUID是用于唯一标识信息的字符串ID的标准化128位格式。 由ATT传送的属性被格式化为特征和服务 。
  • 特性 -A特性包含描述特性值的单个值和0-n个描述符。 一个特性可以被认为是一个类型,类似于类。
  • 描述符 - 描述符是描述特征值的定义属性。 例如,描述符可以指定人类可读的描述,特征值的可接受范围或特征值的特定的测量单位。
  • 服务 - 服务是一个集合的特点。 例如,您可以有一个名为“心率监视器”的服务,其中包括诸如“心率测量”的特征。 您可以在bluetooth.org上找到现有基于GATT的个人资料和服务的列表 。

角色和职责

以下是Android设备与BLE设备互动时适用的角色和职责:

中央与外围。 这适用于BLE连接本身。 处于中心角色的设备扫描,寻找广告,并且外围角色中的设备进行广告。

GATT服务器与GATT客户端。 这决定了两个设备在建立连接后如何相互通信。

BLE权限

首先,需要在manifest中声明使用蓝牙和操作蓝牙的权限

在应用程序清单文件中声明蓝牙权限。 例如:

如果您要声明自己的应用只适用于支持BLE的设备,请在应用清单中包含以下内容:

<uses-feature android:name =“android.hardware.bluetooth_le”android:required =“true”/>

不过,如果您想让应用程式适用于不支援BLE的装置,您仍应在应用的清单中加入这个元素,但required="false"设为required="false" 。

然后在运行时,您可以通过使用PackageManager.hasSystemFeature()确定BLE可用性:

 // Use this check to determine whether BLE is supported on the device.  Then
// you can selectively disable BLE-related features.
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}

在android 6.0 以后,要想获得蓝牙扫描结果,还需要下面的权限

 <manifest ... >
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
...
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
...
</manifest>

设置蓝牙

1.Get the BluetoothAdapter

获得蓝牙适配器

 private BluetoothAdapter mBluetoothAdapter;
...
// Initializes Bluetooth adapter.
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

2.Enable Bluetooth

打开蓝牙

 // Ensures Bluetooth is available on the device and it is enabled.  If not,
// displays a dialog requesting user permission to enable Bluetooth.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

3.初始化BLE蓝牙广播(广告)

(1)广播的设置

(2)设置广播的数据

(3)设置响应的数据

(4)设置连接回调

    private void initGATTServer() {
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setConnectable(true)
.build(); AdvertiseData advertiseData = new AdvertiseData.Builder()
.setIncludeDeviceName(true)
.setIncludeTxPowerLevel(true)
.build(); AdvertiseData scanResponseData = new AdvertiseData.Builder()
.addServiceUuid(new ParcelUuid(UUID_SERVER))
.setIncludeTxPowerLevel(true)
.build(); AdvertiseCallback callback = new AdvertiseCallback() { @Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.d(TAG, "BLE advertisement added successfully");
showText("1. initGATTServer success");
println("1. initGATTServer success");
initServices(getContext());
} @Override
public void onStartFailure(int errorCode) {
Log.e(TAG, "Failed to add BLE advertisement, reason: " + errorCode);
showText("1. initGATTServer failure");
}
}; BluetoothLeAdvertiser bluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
bluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponseData, callback);
}

在被BLE设备连接后,将触发 AdvertiseCallback 的 onStartSuccess,我们在这之后,初始化GATT的服务

4.初始化GATT的服务

(1) 通过 mBluetoothManager.openGattServer() 获得 bluetoothGattServer

(2) 添加 服务,特征,描述。这些内容要让客户端知道。

    private void initServices(Context context) {
bluetoothGattServer = mBluetoothManager.openGattServer(context, bluetoothGattServerCallback);
BluetoothGattService service = new BluetoothGattService(UUID_SERVER, BluetoothGattService.SERVICE_TYPE_PRIMARY); //add a read characteristic.
characteristicRead = new BluetoothGattCharacteristic(UUID_CHARREAD, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
//add a descriptor
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR, BluetoothGattCharacteristic.PERMISSION_WRITE);
characteristicRead.addDescriptor(descriptor);
service.addCharacteristic(characteristicRead); //add a write characteristic.
BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHARWRITE,
BluetoothGattCharacteristic.PROPERTY_WRITE |
BluetoothGattCharacteristic.PROPERTY_READ |
BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_WRITE);
service.addCharacteristic(characteristicWrite); bluetoothGattServer.addService(service);
Log.e(TAG, "2. initServices ok");
showText("2. initServices ok");
}

在 openGattServer 方法中,我们需要传入个回调

bluetoothGattServer = mBluetoothManager.openGattServer(context, bluetoothGattServerCallback);

5.配置数据交互回调

回调时间有:连接状态变化,收发消息,通知消息

/**
* 服务事件的回调
*/
private BluetoothGattServerCallback bluetoothGattServerCallback = new BluetoothGattServerCallback() { /**
* 1.连接状态发生变化时
* @param device
* @param status
* @param newState
*/
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
Log.e(TAG, String.format("1.onConnectionStateChange:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("1.onConnectionStateChange:status = %s, newState =%s ", status, newState));
super.onConnectionStateChange(device, status, newState);
} @Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
Log.e(TAG, String.format("onServiceAdded:status = %s", status));
} @Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, String.format("onCharacteristicReadRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("onCharacteristicReadRequest:requestId = %s, offset = %s", requestId, offset)); bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
// super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
} /**
* 3. onCharacteristicWriteRequest,接收具体的字节
* @param device
* @param requestId
* @param characteristic
* @param preparedWrite
* @param responseNeeded
* @param offset
* @param requestBytes
*/
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {
Log.e(TAG, String.format("3.onCharacteristicWriteRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("3.onCharacteristicWriteRequest:requestId = %s, preparedWrite=%s, responseNeeded=%s, offset=%s, value=%s", requestId, preparedWrite, responseNeeded, offset, OutputStringUtil.toHexString(requestBytes)));
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);
//4.处理响应内容
onResponseToClient(requestBytes, device, requestId, characteristic);
} /**
* 2.描述被写入时,在这里执行 bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS... 收,触发 onCharacteristicWriteRequest
* @param device
* @param requestId
* @param descriptor
* @param preparedWrite
* @param responseNeeded
* @param offset
* @param value
*/
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
Log.e(TAG, String.format("2.onDescriptorWriteRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("2.onDescriptorWriteRequest:requestId = %s, preparedWrite = %s, responseNeeded = %s, offset = %s, value = %s,", requestId, preparedWrite, responseNeeded, offset, OutputStringUtil.toHexString(value))); // now tell the connected device that this was all successfull
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
} /**
* 5.特征被读取。当回复响应成功后,客户端会读取然后触发本方法
* @param device
* @param requestId
* @param offset
* @param descriptor
*/
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
Log.e(TAG, String.format("onDescriptorReadRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("onDescriptorReadRequest:requestId = %s", requestId));
// super.onDescriptorReadRequest(device, requestId, offset, descriptor);
bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
} @Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
Log.e(TAG, String.format("5.onNotificationSent:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("5.onNotificationSent:status = %s", status));
} @Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
super.onMtuChanged(device, mtu);
Log.e(TAG, String.format("onMtuChanged:mtu = %s", mtu));
} @Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
super.onExecuteWrite(device, requestId, execute);
Log.e(TAG, String.format("onExecuteWrite:requestId = %s", requestId));
}
};

6.处理来自客户端发来的数据和发送回复数据:

调用 bluetoothGattServer.notifyCharacteristicChanged 方法,通知数据改变。

        /**
* 4.处理响应内容
*
* @param reqeustBytes
* @param device
* @param requestId
* @param characteristic
*/
private void onResponseToClient(byte[] reqeustBytes, BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, String.format("4.onResponseToClient:device name = %s, address = %s", device.getName(), device.getAddress()));
Log.e(TAG, String.format("4.onResponseToClient:requestId = %s", requestId));
String msg = OutputStringUtil.transferForPrint(reqeustBytes);
println("4.收到:" + msg);
showText("4.收到:" + msg); String str = new String(reqeustBytes) + " hello>";
characteristicRead.setValue(str.getBytes());
bluetoothGattServer.notifyCharacteristicChanged(device, characteristicRead, false); println("4.响应:" + str);
showText("4.响应:" + str);
}

交互流程:

(1) 当客户端开始写入数据时: 触发回调方法 onDescriptorWriteRequest

(2) 在 onDescriptorWriteRequest 方法中,执行下面的方法表示 写入成功 BluetoothGatt.GATT_SUCCESS

       bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);

执行 sendResponse后,会触发回调方法 onCharacteristicWriteRequest

(3) 在 onCharacteristicWriteRequest方法中

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

这个里可以获得 来自客户端发来的数据 requestBytes

(4) 处理响应内容,我写了这个方法:

    onResponseToClient(requestBytes, device, requestId, characteristic);
在这个方法中,通过 bluetoothGattServer.notifyCharacteristicChanged()方法 回复数据

通过日志,我们看看事件触发的顺序

1.onConnectionStateChange:device name = null, address = 74:32:DE:49:3C:28
1.onConnectionStateChange:status = 0, newState =2
2.onDescriptorWriteRequest:device name = null, address = 74:32:DE:49:3C:28
2.onDescriptorWriteRequest:requestId = 1, preparedWrite = false, responseNeeded = true, offset = 0, value = [01,00,],
3.onCharacteristicWriteRequest:device name = null, address = 74:32:DE:49:3C:28
3.onCharacteristicWriteRequest:requestId = 2, preparedWrite=false, responseNeeded=false, offset=0, value=[41,54,45,30,0D,]
4.onResponseToClient:device name = null, address = 74:32:DE:49:3C:28
4.onResponseToClient:requestId = 2
4.收到:ATE0
4.响应:ATE0 hello>
5.onNotificationSent:device name = null, address = 74:32:DE:49:3C:28
5.onNotificationSent:status = 0

代码托管到github:

https://github.com/vir56k/bluetoothDemo 找到 bleperipheraldemo 文件夹

低功耗蓝牙BLE外围模式(peripheral)-使用BLE作为服务端的更多相关文章

  1. 低功耗蓝牙4.0BLE编程-nrf51822开发(7)-SDP服务发现协议

    SDP的全称是Service Discovery Protocol,中文是服务发现协议.SDP(服务发现协议)是蓝牙协议体系中的核心协议,是蓝牙系统重要组成部分,是所有用户模式的基础.在蓝牙系统中.客 ...

  2. Newbe.Claptrap - 一套以 “事件溯源” 和“Actor 模式”作为基本理论的服务端开发框架

    本文是关于 Newbe.Claptrap 项目主体内容的介绍,读者可以通过这篇文章,大体了解项目内容. 轮子源于需求 随着互联网应用的蓬勃发展,相关的技术理论和实现手段也在被不断创造出来.诸如 “云原 ...

  3. 深入浅出讲解低功耗蓝牙(BLE)协议栈

    详解BLE连接建立过程https://www.cnblogs.com/iini/p/8972635.html 详解BLE 空中包格式—兼BLE Link layer协议解析https://www.cn ...

  4. Android 低功耗蓝牙BLE 开发注意事项

    基本概念和问题 1.蓝牙设计范式? 当手机通过扫描低功耗蓝牙设备并连接上后,手机与蓝牙设备构成了客户端-服务端架构.手机通过连接蓝牙设备,可以读取蓝牙设备上的信息.手机就是客户端,蓝牙设备是服务端. ...

  5. Android使用BLE(低功耗蓝牙,Bluetooth Low Energy)

    背景 在学习BLE的过程中,积累了一些心得的DEMO,放到Github,形成本文.感兴趣的同学可以下载到源代码. github: https://github.com/vir56k/bluetooth ...

  6. 永不消逝的电波(三):低功耗蓝牙(BLE)入门之如何调戏别人的小米手环

    0×00 前言 蓝牙(Bluetooth),一种无线技术标准,用来让固定与移动设备,在短距离间交换数据,以形成个人局域网(PAN).其使用短波特高频(UHF)无线电波,经由2.4至2.485 GHz的 ...

  7. Android低功耗蓝牙(BLE)开发的一点感受

    最近一段时间,因为产品的需要我做了一个基于低功耗蓝牙设备的Android应用,其中碰到了一些困难,使我深深体会到Android开发的难处:不同品牌,不同型号和不同版本之间的差异使得Android应用适 ...

  8. 深入浅出低功耗蓝牙(BLE)协议栈

    深入浅出低功耗蓝牙(BLE)协议栈 BLE协议栈为什么要分层?怎么理解蓝牙"连接"?如果蓝牙协议只有ATT没有GATT会发生什么? 协议栈框架 一般而言,我们把某个协议的实现代码称 ...

  9. TI低功耗蓝牙(BLE)介绍

    TI低功耗蓝牙(BLE)介绍 本文档翻译和修改自参考资料:CC2540Bluetooth Low Energy Software Developer’s Guide (Rev. B),部分图片直接引用 ...

随机推荐

  1. ROWNUMBER()、RANK()、DENSE_RANK()、NTILE1

    SQLServer针对排名函数ROWNUMBER().RANK().DENSE_RANK().NTILE的研究!~ 相信大家在软件工程中经常会遇到对某些数据进行排名的问题,尤其是对于电子商务的HR来说 ...

  2. 学习Linux(一)环境搭建

    零基础学习Linux(一)环境搭建 从本文开始我会为大家介绍一下linux环境下详细的集群环境安装.配置.部署到实例演示的整个过程.在此过程中会给大家详细介绍一下Linux的操作技巧和一些工具的使用. ...

  3. EasyNet.Solr 4.4.0发布及例子

    EasyNet.Solr 4.4.0发布及例子 EasyNet.Solr 4.4.0已经发布,可以直接从http://easynet.codeplex.com/ 下载试用并反馈.最新版本进行了以下改动 ...

  4. #define命令的一些高级用法

    =========================================================== define中的三个特殊符号:#,##,#@ ================= ...

  5. iOS多线程的初步研究(六)

    iOS多线程的初步研究(六) iOS平台提供更高级的并发(异步)调用接口,让你可以集中精力去设计需完成的任务代码,避免去写与程序逻辑无关的线程生成.运行等管理代码.当然实质上是这些接口隐含生成线程和管 ...

  6. PHP语言基础03 By ACReaper

    03.01逻辑运算符 &&,and ||,or xor 这里只强调一点用and或者or的话,记得加上括号,不然可能出错,至于原因我也不是很清楚,反正尽量直接用&&,|| ...

  7. struts升级到最高版本后遇到的问题。关于actionmessage传递问题。

    Struts2升级到最新版本遇到的一些问题 首先是更换对应的jar,如asm.common.ongl.struts等等.更换后发现系统启动不了,按照网上的介绍,先后又更新了slf4j-log4j12- ...

  8. JS高程5.引用类型(5)Array类型的操作方法

    一.操作方法 1.concat()方法 基于当前数组中的所有项创建一个新数组.具体说,是先创建当前数组的一个副本,然后将接收到的参数添加到这个副本的末尾,最后返回新构建的数组.在没有给concat() ...

  9. 怎样求FIRST集、FOLLOW集和SELECT集

    一,要知道什么是终结符和非终结符. 终结符:通俗的说就是不能单独出现在推导式左边的符号,也就是说终结符不能再进行推导. 非终结符:不是终结符的都是非终结符.(非男即女,呵呵) 如:A-->B,则 ...

  10. 【Scala】Scala之String

    一.前言 大数据领域的Spark.Kafka.Summingbird等都是由Scala语言编写而成,相比Java而言,Scala更精炼.由于笔者从事大数据相关的工作,所以有必要好好学习Scala语言, ...