Android BLE与终端通信(五)——Google API BLE4.0低功耗蓝牙文档解读之案例初探

Android BLE与终端通信(五)——Google API BLE4.0低功耗蓝牙文档解读之案例初探


算下来很久没有写BLE的博文了,上家的技术都快忘记了,所以赶紧读了一遍Google的API顺便写下这篇博客心得

其实大家要学习Android的技术,Google的API就是最详细的指导书了,而且通俗易懂,就算看不懂英语,翻译翻译再结合代码也能看个大概的,真的很赞哟,我们直接解读吧!

Google的API中前面就是一大片的概念,这其实在我之前的篇幅中都有说过的

一.概述

我们直接翻译

  • Generic Attribute Profile(GATT)—GATT配置文件是一个通用规范,用于在BLE链路上发送和接收被称为“属性”的数据块。目前所有的BLE应用都基于GATT。 蓝牙SIG规定了许多低功耗设备的配置文件。配置文件是设备如何在特定的应用程序中工作的规格说明。注意一个设备可以实现多个配置文件。例如,一个设备可能包括心率监测仪和电量检测。
  • Attribute Protocol(ATT)—GATT在ATT协议基础上建立,也被称为GATT/ATT。ATT对在BLE设备上运行进行了优化,为此,它使用了尽可能少的字节。每个属性通过一个唯一的的统一标识符(UUID)来标识,每个String类型UUID使用128 bit标准格式。属性通过ATT被格式化为characteristics和services。
  • Characteristic 一个characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以被认为是一个类型,类似于类。
  • Descriptor Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的测量单位。
  • Service service是characteristic的集合。例如,你可能有一个叫“Heart Rate Monitor(心率监测仪)”的service,它包括了很多characteristics,如“heart rate measurement(心率测量)”等。你可以在bluetooth.org 找到一个目前支持的基于GATT的配置文件和服务列表。

二.角色和责任

这里有两个概念

  • 中央 VS 外围设备。 适用于BLE连接本身。中央设备扫描,寻找广播;外围设备发出广播。
  • GATT 服务端 VS GATT 客户端。决定了两个设备在建立连接后如何互相交流。

为了方便理解,想象你有一个Android手机和一个用于活动跟踪BLE设备,手机支持中央角色,活动跟踪器支持外围(为了建立BLE连接你需要注意两件事,只支持外围设备的两方或者只支持中央设备的两方不能互相通信)。
当手机和运动追踪器建立连接后,他们开始向另一方传输GATT数据。哪一方作为服务器取决于他们传输数据的种类。例如,如果运动追踪器想向手机报告传感器数据,运动追踪器是服务端。如果运动追踪器更新来自手机的数据,手机会作为服务端。
在这份文档的例子中,android app(运行在android设备上)作为GATT客户端。app从gatt服务端获得数据,gatt服务端即支持Heart Rate Profile(心率配置)的BLE心率监测仪。但是你可以自己设计android app去扮演GATT服务端角色。也就是自定义

三.案例

好的,Google文档中,也举了一个例子说明,我们要使用BLE的时候,必须有要加上两个权限

<!--蓝牙权限-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

这其实你要使用蓝牙这个硬件都是要加上这个权限的,但是这里Google又声明了一点

如果n你想声明你的软件只为具有BLE的设备提供服务的话,你应该要在清单文件中加入

    <!--只为BLE提供服务-->
    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

如果改为false的话,那其他蓝牙也是可以使用的,我们创建一个工程——BLETest

和传统蓝牙一样,我们添加完权限之后就要去判断这个设备是否支持BLE

//判断是否支持BLE设备
if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Toast.makeText(this, "此设备不支持BLE", Toast.LENGTH_SHORT).show();
      finish();
   }

这步操作也只是你设置为false的时候才是必须的,因为你如果你设置为true,那你只给BLE服务,那这个判断也就是多余的了,紧接着,我们还需要去判断蓝牙是否开启,如果没有开启,我们就去开启他,这次虽然也是用BluetoothAdapter 去获取,但是这里用了一个新的类BluetoothManager ,先初始化

//初始化蓝牙适配器
BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

然后再去开启

//打开蓝牙
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }

这里的REQUEST_ENABLE_BT也就是一个回调的标志,无须理会

 public static final int REQUEST_ENABLE_BT = 0;

写了这个之后,当我们蓝牙没有开启的时候就会去友好的提示用户开启了

我们现在准备工作都有了之后我们就可以直接去搜索设备了,BLE的api和普通蓝牙的API还是有一定的区别的,就是有回调了,使用起来挺方便的,但是搜索是很费电的,所以切记要小心使用,最好是找到设备之后就停止扫描或者设置扫描时间,不然你就苦逼了

//搜索设备
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
    private void scanLeDevice(final boolean enable) {
        if (enable) {
            // 经过预定扫描期后停止扫描
            mHandler.postDelayed(new Runnable() {
                @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
                @Override
                public void run() {
                    mScanning = false;
                    mBluetoothAdapter.stopLeScan(mLeScanCallback);
                }
            }, SCAN_PERIOD);
            mScanning = true;
            mBluetoothAdapter.startLeScan(mLeScanCallback);
        } else {
            mScanning = false;
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }

    }

这样,当我们接收到搜索设备的回调时便可以直接添加在Adapter上,要注意,所演示的也是Google提供的Demo

//搜索设备的回调
    private BluetoothAdapter.LeScanCallback mLeScanCallback =
            new BluetoothAdapter.LeScanCallback() {

                @Override
                public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mLeDeviceListAdapter.addDevice(device);
                            mLeDeviceListAdapter.notifyDataSetChanged();
                        }
                    });
                }
            };

连接GATT服务端

这仅仅是第一步,我们要让BLE拥有强大的能力,就需要连接他的GATT服务端

    /**
     * 1.上下文
     * 2.自动连接
     * 3.BluetoothGattCallback回调
     */
    mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

连接到GATT服务端时,由BLE设备做主机,并返回一个BluetoothGatt实例,然后你可以使用这个实例来进行GATT客户端操作。请求方(软件)是GATT客户端。BluetoothGattCallback用于传递结果给用户,例如连接状态,以及任何进一步GATT客户端操作。
在这个例子中,这个BLE APP提供了一个activity(DeviceControlActivity)来连接,显示数据,显示该设备支持的GATT services和characteristics。根据用户的输入,这个activity与BluetoothLeService通信,通过Android BLE API实现与BLE设备交互。

package com.lgl.bletest;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.util.Log;

import java.util.UUID;

/**
 * Created by LGL on 2016/5/13.
 */
public class BluetoothLeService extends BluetoothClass.Service {

    private BluetoothManager mBluetoothManager; //蓝牙管理器
    private BluetoothAdapter mBluetoothAdapter; //蓝牙适配器
    private String mBluetoothDeviceAddress; //蓝牙设备地址
    private BluetoothGatt mBluetoothGatt;
    private int mConnectionState = STATE_DISCONNECTED;
    private static final int STATE_DISCONNECTED = 0; //设备无法连接
    private static final int STATE_CONNECTING = 1;  //设备正在连接状态
    private static final int STATE_CONNECTED = 2;   //设备连接完毕
    public final static String ACTION_GATT_CONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
    public final static String ACTION_GATT_DISCONNECTED =
            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
    public final static String ACTION_GATT_SERVICES_DISCOVERED =
            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
    public final static String ACTION_DATA_AVAILABLE =
            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
    public final static String EXTRA_DATA =
            "com.example.bluetooth.le.EXTRA_DATA";
    public final static UUID UUID_HEART_RATE_MEASUREMENT =
            UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
    //通过BLE API的不同类型的回调方法
    private final BluetoothGattCallback mGattCallback =
            new BluetoothGattCallback() {
                @Override
                public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                                    int newState) {//当连接状态发生改变
                    String intentAction;
                    if (newState == BluetoothProfile.STATE_CONNECTED) {//当蓝牙设备已经连接
                        intentAction = ACTION_GATT_CONNECTED;
                        mConnectionState = STATE_CONNECTED;
                        broadcastUpdate(intentAction);
                        Log.i(TAG, "Connected to GATT server.");
                        Log.i(TAG, "Attempting to start service discovery:" +
                                mBluetoothGatt.discoverServices());
                    } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//当设备无法连接
                        intentAction = ACTION_GATT_DISCONNECTED;
                        mConnectionState = STATE_DISCONNECTED;
                        Log.i(TAG, "Disconnected from GATT server.");
                        broadcastUpdate(intentAction);
                    }
                }

                @Override
                // 发现新服务端
                public void onServicesDiscovered(BluetoothGatt gatt, int status) {
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
                    } else {
                        Log.w(TAG, "onServicesDiscovered received: " + status);
                    }
                }

                @Override
                // 读写特性
                public void onCharacteristicRead(BluetoothGatt gatt,
                                                 BluetoothGattCharacteristic characteristic,
                                                 int status) {
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
                    }
                }

            };
}

当触发特定的回调时,它调用适当的broadcastUpdate()辅助方法,通过这一个动作。注意,本节中的数据解析执行按照蓝牙心率测量概要文件规范:

private void broadcastUpdate(final String action) {
    final Intent intent = new Intent(action);
    sendBroadcast(intent);
}

private void broadcastUpdate(final String action,
                             final BluetoothGattCharacteristic characteristic) {
    final Intent intent = new Intent(action);

    // This is special handling for the Heart Rate Measurement profile. Data
    // parsing is carried out as per profile specifications.
    if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
        int flag = characteristic.getProperties();
        int format = -1;
        if ((flag & 0x01) != 0) {
            format = BluetoothGattCharacteristic.FORMAT_UINT16;
            Log.d(TAG, "Heart rate format UINT16.");
        } else {
            format = BluetoothGattCharacteristic.FORMAT_UINT8;
            Log.d(TAG, "Heart rate format UINT8.");
        }
        final int heartRate = characteristic.getIntValue(format, 1);
        Log.d(TAG, String.format("Received heart rate: %d", heartRate));
        intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
    } else {
        // For all other profiles, writes the data formatted in HEX.
        final byte[] data = characteristic.getValue();
        if (data != null && data.length > 0) {
            final StringBuilder stringBuilder = new StringBuilder(data.length);
            for(byte byteChar : data)
                stringBuilder.append(String.format("%02X ", byteChar));
            intent.putExtra(EXTRA_DATA, new String(data) + "\n" +
                    stringBuilder.toString());
        }
    }
    sendBroadcast(intent);
}

返回DeviceControlActivity, 这些事件由一个BroadcastReceiver来处理:

// Handles various events fired by the Service.
// ACTION_GATT_CONNECTED: connected to a GATT server.
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
// ACTION_DATA_AVAILABLE: received data from the device. This can be a
// result of read or notification operations.
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
            mConnected = true;
            updateConnectionState(R.string.connected);
            invalidateOptionsMenu();
        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
            mConnected = false;
            updateConnectionState(R.string.disconnected);
            invalidateOptionsMenu();
            clearUI();
        } else if (BluetoothLeService.
                ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
            // Show all the supported services and characteristics on the
            // user interface.
            displayGattServices(mBluetoothLeService.getSupportedGattServices());
        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
        }
    }
};

读取BLE变量

你的android app完成与GATT服务端连接和发现services后,就可以读写支持的属性。例如,这段代码通过服务端的services和 characteristics迭代,并且将它们显示
在UI上。

public class DeviceControlActivity extends Activity {
    ...
    // Demonstrates how to iterate through the supported GATT
    // Services/Characteristics.
    // In this sample, we populate the data structure that is bound to the
    // ExpandableListView on the UI.
    private void displayGattServices(List<BluetoothGattService> gattServices) {
        if (gattServices == null) return;
        String uuid = null;
        String unknownServiceString = getResources().
                getString(R.string.unknown_service);
        String unknownCharaString = getResources().
                getString(R.string.unknown_characteristic);
        ArrayList<HashMap<String, String>> gattServiceData =
                new ArrayList<HashMap<String, String>>();
        ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                = new ArrayList<ArrayList<HashMap<String, String>>>();
        mGattCharacteristics =
                new ArrayList<ArrayList<BluetoothGattCharacteristic>>();

        // / 循环可用的Characteristics.
        for (BluetoothGattService gattService : gattServices) {
            HashMap<String, String> currentServiceData =
                    new HashMap<String, String>();
            uuid = gattService.getUuid().toString();
            currentServiceData.put(
                    LIST_NAME, SampleGattAttributes.
                            lookup(uuid, unknownServiceString));
            currentServiceData.put(LIST_UUID, uuid);
            gattServiceData.add(currentServiceData);

            ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                    new ArrayList<HashMap<String, String>>();
            List<BluetoothGattCharacteristic> gattCharacteristics =
                    gattService.getCharacteristics();
            ArrayList<BluetoothGattCharacteristic> charas =
                    new ArrayList<BluetoothGattCharacteristic>();
           // Loops through available Characteristics.
            for (BluetoothGattCharacteristic gattCharacteristic :
                    gattCharacteristics) {
                charas.add(gattCharacteristic);
                HashMap<String, String> currentCharaData =
                        new HashMap<String, String>();
                uuid = gattCharacteristic.getUuid().toString();
                currentCharaData.put(
                        LIST_NAME, SampleGattAttributes.lookup(uuid,
                                unknownCharaString));
                currentCharaData.put(LIST_UUID, uuid);
                gattCharacteristicGroupData.add(currentCharaData);
            }
            mGattCharacteristics.add(charas);
            gattCharacteristicData.add(gattCharacteristicGroupData);
         }
    ...
    }
...
}

接收GATT通知

当设备上的特性改变时会通知BLE应用程序。这段代码显示了如何使用setCharacteristicNotification( )给一个特性设置通知。

//GATT服务端
private BluetoothGatt mBluetoothGatt;
BluetoothGattCharacteristic characteristic;
//是否打开
boolean enabled;
...
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
...
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
        UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);

如果对一个特性启用通知,当远程蓝牙设备特性发送变化,回调函数onCharacteristicChanged( ))被触发。

@Override
// 广播更新
public void onCharacteristicChanged(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic) {
    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}

关闭客户端App

当你的app完成BLE设备的使用后,应该调用close( ),系统可以合理释放占用资源。

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;
}

代码可能有点乱,这里提供一个Google的Demo功大家测试

Demo下载地址:http://download.csdn.net/detail/qq_26787115/9519652

时间: 2024-10-26 02:00:27

Android BLE与终端通信(五)——Google API BLE4.0低功耗蓝牙文档解读之案例初探的相关文章

Android BLE与终端通信(一)——Android Bluetooth基础API以及简单使用获取本地蓝牙名称地址

Android BLE与终端通信(一)--Android Bluetooth基础API以及简单使用获取本地蓝牙名称地址 Hello,工作需要,也必须开始向BLE方向学习了,公司的核心技术就是BLE终端通信技术,无奈一直心不在此,但是真当自己要使用的时候还是比较迷茫,所以最近也有意向来学习这一块,同时,把自己的学习经历分享出来 一.摘要 Android智能硬件前几年野一直不温不火的,到了现在却热火朝天了,各种智能手环,智能手表,智能家居等,而使用BLE这个方向也越来越多,而这方面的资料却是真的很少

Android BLE与终端通信(三)——客户端与服务端通信过程以及实现数据通信

Android BLE与终端通信(三)--客户端与服务端通信过程以及实现数据通信 前面的终究只是小知识点,上不了台面,也只能算是起到一个科普的作用,而同步到实际的开发上去,今天就来延续前两篇实现蓝牙主从关系的客户端和服务端了,本文相关链接需要去google的API上查看,需要翻墙的 Bluetooth Low Energy:http://developer.android.com/guide/topics/connectivity/bluetooth-le.html 但是我们依然没有讲到BLE(

Android BLE与终端通信(四)——实现服务器与客户端即时通讯功能

Android BLE与终端通信(四)--实现服务器与客户端即时通讯功能 前面几篇一直在讲一些基础,其实说实话,蓝牙主要为多的还是一些概念性的东西,当你把概念都熟悉了之后,你会很简单的就可以实现一些逻辑,主要是Socket和I/O流的操作,今天就来一起做一个聊天的小程序,我们都知道,我们实现蓝牙连接,蓝牙是有主从关系的,所以有客户端和服务端之分,我们新建一个工程--BLE_QQ(hh,毕竟是即时通讯嘛,和QQ挨个边) 参考Google的API:http://developer.android.c

Android BLE与终端通信(二)——Android Bluetooth基础科普以及搜索蓝牙设备显示列表

Android BLE与终端通信(二)--Android Bluetooth基础搜索蓝牙设备显示列表 摘要 第一篇算是个热身,这一片开始来写些硬菜了,这篇就是实际和蓝牙打交道了,所以要用到真机调试哟,这篇我会把基本上要讲的概念都通俗易懂的来一遍,这样我们脑子里先有个逻辑,我们就好操作了,先看一下我们的剖析图 下面概念相关的可去原文查看:http://www.epx.com.br/artigos/bluetooth_gatt.php 一.蓝牙简介 蓝牙这个名称来自于第十世纪的一位丹麦国王哈拉尔蓝牙

azure-Rest API 列出Blob 代码或者文档都可以

问题描述 Rest API 列出Blob 代码或者文档都可以 最近需要用rest api去list Azure存储容器里的blob 有没有现成的代码文档可以让我参考参考 解决方案 Hi 关于Rest API的编写,首先我们找出Rest API的详细的说明文档,你可以在这个地址寻找:http://www.windowsazure.cn/zh-cn/develop/reference/,找到后我们只需要按照文档编写好一些请求的信息即可.需要特别注意的是每个请求都需要被验证,关于Azure存储服务验证

开发API完成,写个文档

Jira对接Prism开发API指南   部门 证系统运维团队 文档制作人 陈刚() 时间 2017-04-05 版本 第一版     目录 目的... 1 通例:... 1 认证... 2 新建版本单... 2 获取指定版本单的发布单信息... 3     目的 为了提升工作效率,打通jira和prism之间的联系,让软件项目管理人员可以在jira上新建版本单,并跟踪发布进度,特在prism上制作相关API供jira调用.   通例: l  Prism的web址会因环境不同或构架变更而发生变更

使用Apache POI和OpenOffice API获取微软Office系列文档的页数

这常常会导致用户无法获得正确的 Word 文档页数.对此,我们对 Apache POI 和 OpenOffice API 进行简要的介绍,并结合使用 Apache POI 和 OpenOffice API 开发了一套获取微软 Office 系列文档(WORD, EXCEL and POWERPOINT)的页数的解决方案,以方便广大的开发人员快速方便地应用到自己的项目中. Apache POI 是一套用于访问微软 Office 格式文档 (Word, Excel and PowerPoint) 的

窗口通信-【MFC分割窗口后的单文档怎么变为多文档??】

问题描述 [MFC分割窗口后的单文档怎么变为多文档??] 网上的办法使过,好像不行:原因可能就是因为窗口被分割了的原因,我将分割窗口的过程全部屏蔽掉以后从单文档变为多文档没有问题.如果加上窗口分割以后就不能变为多文档了.下面是单文档和多文档的差别之一,我的问题也就是在这里.[其实我问题的原因是这样的,想在菜单中弄一个新建文档的命令,而多文档刚好可以解决这个问题,如果能解决这个问题也照样给分] 解决方案 //多文档CMultiDocTemplate* pDocTemplate;pDocTempla

smarty3.0中文手册文档API及使用指南

Smarty2.0在线手册 http://www.php100.com/manual/smarty Smarty3.0在线手册 http://www.php100.com/manual/smarty3   1.安装Smarty3.0 一.什么是smarty? smarty是一个使用PHP写出来的模板PHP模板引擎,它提供了逻辑与外在内容的分离,简单的讲, 目的就是要使用PHP程序员同美工分离,使用的程序员改变程序的逻辑内容不会影响到美工的页面设计, 美工重新修改页面不会影响到程序的程序逻辑,这在