分类目录归档:Android-BLE

搞定Android Bluetooth Low Energy-04

蓝牙读写

蓝牙连接成功之后,可以对services进行读写。由于services是存在蓝牙设备中的,需要调用discoverServices()来发现蓝牙服务。
读写数据主要一下几种,读写和扫描一样都是异步的。

  • writeCharacteristic()
  • writeDescriptor()
  • setCharacteristicNotification()
  • readCharacteristic()
  • readDescriptor()
  • readRemoteRssi()
BluetoothGatt.java
//通过characteristic,把value写给蓝牙设备
characteristic.setValue(byte[] value)
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic)

BluetoothGattCallback.java
//写完回调方法
public void onCharacteristicWrite (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)


//BluetoothGattDescriptor 和 BluetoothGattCharacteristic操作类似,只是不同类型,descriptor一般属于某个characteristic。
BluetoothGatt.java
//通过descriptor,把value写给蓝牙设备
descriptor.setValue(byte[] value)
public boolean writeDescriptor (BluetoothGattDescriptor descriptor)

BluetoothGattCallback.java
//写完回调方法
public void onDescriptorWrite (BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status)



//开启notification相对麻烦点
//开启notifications需要两步, 首页本地开启监听, 然后远程蓝牙设备也要开启。
private static void enableMagnetometerNotifications(BluetoothGatt bluetoothGatt) {
    BluetoothGattService service = bluetoothGatt.getService(uuid1);
    BluetoothGattCharacteristic c = service.getCharacteristic(uuid2);
    bluetoothGatt.setCharacteristicNotification(c, true); //本地开启

    BluetoothGattDescriptor config = c.getDescriptor(uuid3);
    config.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    bluetoothGatt.writeDescriptor(config); //远程开启
}

BluetoothGattCallback.java
//notification回调方法
public void onCharacteristicChanged (BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)

Services、Characteristics、Descriptors之间的关系

gatt_ccc

  1. BluetoothGatt.discoverServices()发现蓝牙所有服务
  2. BluetoothGatt.getService (UUID uuid) 获取到具体服务
  3. BluetoothGattService.getCharacteristic (UUID uuid) 获取到具体Characteristic
  4. BluetoothGattCharacteristic.getDescriptor (UUID uuid)获取到具体Descriptor

开发建议

  1. 有些手机服务会缓存,再特殊情况蓝牙服务会改变的情况下,需要强制refresh。
  2. 神奇的129(0x81)错误码:Android Issue 156730
  3. 写数据不要并发,最好做个消息队列,写完成了再写下一个:133(0x85)错误码。
  4. BLE默认可以发送最大20字节
  5. Android-4-3-bluetooth-low-energy-unstable

参考文献

搞定Android Bluetooth Low Energy-03

连接蓝牙设备

BluetoothDevice,扫描到的远程蓝牙设备,可以进行连接connectGatt(),也可以查询设备信息(如:名称、地址、配对状态)
可以通过Mac地址得到BluetoothDevice, BluetoothAdapter.getRemoteDevice(mac)
也可以通过系统配对的蓝牙设备中,获取到BluetoothAdapter.getBondedDevices()

connectGatt

BluetoothDevice.java
/**
 *参数autoConnect:当设备有效(可连接)时,系统是否自动去连接设备。
 *true:系统就会发起一个后台连接,等到系统发现了一个有效设备,就会自动连上,通常这个过程是非常慢的。
 *false:就会直接连接,通常会比较快。
*/
public BluetoothGatt connectGatt (Context context, boolean autoConnect, BluetoothGattCallback callback)

BluetoothGatt.java
// 通过connectGatt连接成功,中途断开重连蓝牙,不需要再次扫描了。有可能失败~
public boolean connect ()
// 断开连接
public void disconnect()
// 关闭Bluetooth GATT client
public void close()

BluetoothGattCallback.java
// 蓝牙连接状态变化
public void onConnectionStateChange (BluetoothGatt gatt, int status, int newState)

连接蓝牙实例

    protected BluetoothGatt mGatt = null;
    //device 连接
    mGatt = device.connectGatt(context, false, mGattCallback);

    protected BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                //连接成功
                
            } else {
                //连接断开

            }
        }

    };

开发建议

1. connectGatt(),connect(),disconnect(),discoverServices()使用UI线程 Samsung Galaxy S4 BUG
2. BLE开发的各种坑
3. Android Bug:BluetoothGatt.connect() doesn’t work on Nexus devices

搞定Android Bluetooth Low Energy-02

发现蓝牙设备

一般情况下,要连接蓝牙设备,先要发现蓝牙设备,也就是进行蓝牙扫描。
Peripheral devices(一直发送蓝牙广播,例如:手环)
Central devices(扫描接收Peripheral devices的广播,例如:Android手机)
advertising_and_discovery    广播示意图

这个有点类似ARP(Address Resolution Protocol,地址解析协议),不过ARP高级多了,但都是为了得到目标设备的通信信息。

Android蓝牙扫描

Android4.3/4.4存在一些bug和不稳定性,Android5.0出了新的API。

Android4.3/4.4
BluetoothAdapter.java
//开始扫描,扫描所有附近的蓝牙设备,扫描结果在回调:onLeScan
public boolean startLeScan (BluetoothAdapter.LeScanCallback callback)
//开始扫描广播中指定uuid service的设备,但是对于128位的UUID,这个方法无效。
public boolean startLeScan (UUID[] serviceUuids, BluetoothAdapter.LeScanCallback callback)
//由于扫描是耗电操作,完成后尽早结束扫描。
public void stopLeScan (BluetoothAdapter.LeScanCallback callback)

BluetoothAdapter.LeScanCallback.java
//扫描结果回调,回调是子线程。
public abstract void onLeScan (BluetoothDevice device, int rssi, byte[] scanRecord)
Android5.0及以上
BluetoothLeScanner.java
public void startScan (ScanCallback callback)
public void startScan (List<ScanFilter> filters, ScanSettings settings, ScanCallback callback)
public void stopScan (ScanCallback callback)

ScanCallback.java
public void onScanResult (int callbackType, ScanResult result)
public void onScanFailed (int errorCode)
Android扫描强烈建议:

0. 权限:BLUETOOTH and BLUETOOTH_ADMIN
1. 扫描是耗电操作,扫描完毕立马结束扫描。
2. 扫描是耗电操作,不要一直扫描,设置超时时间(当设备不在的时候,纯属浪费)。
3. 有些手机,一次扫描结果只上报一次而且device.getName()可能返回为null,如果想再次上报扫描,需要重新开始扫描。
4. 扫描结果回调是子线程。
5. startLeScan()有可能失败,需要关注return值。
6. Android6.0还需要权限:ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION

Android bug:BLE filtering in startLeScan(UUIDs, callback) doesn’t work for 128-bit UUIDs

Android蓝牙扫描实例

权限配置:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
库依赖

Add it in your root build.gradle at the end of repositories:

allprojects {
	repositories {
		...
		maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
	}
}
Add the dependency
dependencies {
    compile 'com.github.captain-miao:ble:1.0.0-SNAPSHOT'
}
几行搞定扫描
BleScanner mBleScanner = new BleScanner(getContext(), new SimpleScanCallback() {
    @Override
    public void onBleScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        
    }

    @Override
    public void onBleScanFailed(BleScanState scanState) {

    }
});

//开始扫描 10秒超时
mBleScanner.startBleScan(10 * 1000);

//停止扫描
mBleScanner.stopBleScan();

Android6.0 权限处理参考:

Android M(6.0) 权限爬坑之旅

搞定Android Bluetooth Low Energy-01

在智能穿戴设备风起云涌的时代,搞定BLE还是可以谋得比较好的待遇的(虽然穿戴设备玩坏了众筹,智能穿戴也玩坏了硬件)。
国内的智能设备举例:土曼手表WeLoop小米智能设备WeCoachbonginwatchticwear

Bluetooth Low Energy简介

Android 4.3(API 18)才正式支持BLE,也称蓝牙4.0。Android提供了相应的API,可以扫描设备、连接设备,设备通讯。对比传统的蓝牙,BLE的设计能够显著减低功耗。手机完全可以一直开着蓝牙。

BLE协议栈
bluetooth-protocol-stack
Host:属于软件部分
Controller:属于硬件部分
PHY : 物理层 负责2.4 GHz信号传输
LL : 链路层 controls the RF state of the transceiver, determining whether it’s advertising, scanning, initiating, connected, or standing by.
HCI :负责Host和Controller之间的通信,类似硬件驱动层。
L2CAP:逻辑控制和适配层 负责数据包分段和重组,以及QoS等。
ATT : enables a device to reveal certain of its attributes to another device. The ATT block sets up peer-to-peer communication between an attribute server and a client to be able to exchange this information over a dedicated L2CAP channel.
SM : 安全管理(密码、加密啥的)
GATT:defines the sub-procedures for using ATT and specifies the structure of Bluetooth profiles. All communications between Bluetooth devices are handled through GATT sub-procedures.
GAP :provides the interface between the application and Bluetooth profiles and handles device discovery, connection, and services, including security procedures.
以上诸多翻译不能(老古董定的协议),目前App开发核心理解GAP、GATT和ATT。

Generic Access Profile(GAP)

GAP主要完成蓝牙设备发现、蓝牙设备连接、蓝牙服务发现,主要的两个角色:Central devices and Peripheral devices

  • Peripheral devices

Peripherals devices发送广播包,广播包可以包含一些有用的信息: 设备名称、设备mac地址(iOS不能获取蓝牙设备mac地址)等

  • Central devices

Central devices, 扫描和监听Peripherals devices的广播包如下图,扫描到目标设备后, Central device可以发起连接Peripheral device请求advertising_and_discovery
当peripheral and a central device连接成功后, 就可以使用GATT services 和 characteristics 通讯了.

Attribute Protocol(ATT)

The sole building block of ATT is the attribute. An attribute is composed by three elements:

  1. a 16-bit handle;
  2. an UUID which defines the attribute type;
  3. a value of a certaing length.
Handle UUID Value
0x0105 0x2902 0x0000

Most of the ATT protocol is pure client-server: client takes the initiative, server answers. But ATT has notification and indication capabilities, in which the server takes the initiative of notifying a client that an attribute value has changed, saving the client from having to poll the attribute.

Generic Attribute Profile(GATT)

GATT is a base profile for all top-level LE profiles. It defines how a bunch of ATT attributes are grouped together into meaningful services.

GATT两个角色

Client

The GATT client corresponds to the ATT client discussed in Attribute Protocol (ATT). It sends requests to a server and receives responses (and server-initiated updates) from it. The GATT client does not know anything in advance about the server’s attributes, so it must first inquire about the presence and nature of those attributes by performing service discovery. After completing service discovery, it can then start reading and writing attributes found in the server, as well as receiving server-initiated updates.

Server

The GATT server corresponds to the ATT server discussed in Attribute Protocol (ATT). It receives requests from a client and sends responses back. It also sends server-initiated updates when configured to do so, and it is the role responsible for storing and making the user data available to the client, organized in attributes. Every BLE device sold must include at least a basic GATT server that can respond to client requests, even if only to return an error response.
It is worth mentioning once more that GATT roles are both completely independent of GAP roles (see Roles) and also concurrently compatible with each other. That means that both a GAP central and a GAP peripheral can act as a GATT client or server, or even act as both at the same time.


GATT 结构图

Services

Service is collections of characteristics and relationships to other services that encapsulate the behavior of part of a device.

Characteristics

A characteristic is a value used in a service along with properties and configuration information about how the value is accessed and information about how the value is displayed or represented.

Descriptors

Characteristic descriptors are used to contain related information about the characteristic Value.

All the Information of services, characteristics and descriptors are combined together and made a Profile. Created profile data is then converted into HEX format file which is non readable by humans for security aspects. This File is used as Input for configure GATT Layer.

参考文献

自己写的一个Android BLE 工具

apk下载地址

http://fir.im/bleYan

screenshot

为了验证蓝牙库(轻松搞定Android蓝牙4.0(Bluetooth Low Energy))是否好用,也是拼了。

支持的功能

  • Android 5.0风格
  • 扫描蓝牙设备
  • 可以写蓝牙命令
  • 可以开启Notify
  • 可以读取characteristic特征值
  • Notify接收的数据可以Share
  • 蓝牙设备可收藏和设置别名
  • 蓝牙命令可以管理

todo 列表

  • 蓝牙日志输出

缺陷

  • 多个Activity跳转,蓝牙回调没有共享,导致连接不能共享(有好的方案了再来实现)

源码地址

GitHub地址:https://github.com/captain-miao/bleYan

轻松搞定Android蓝牙4.0(Bluetooth Low Energy)

基础知识参考:Android 蓝牙4.0(1)

Android 蓝牙3大操作

  • 扫描蓝牙设备
  • 连接蓝牙设备
  • 蓝牙蓝牙通信

一、扫描蓝牙设备

蓝牙扫描Android4.x方式:startLeScan (LeScanCallback)
蓝牙扫描Android5.x方式:startScan (ScanCallback callback)
简化扫描操作:像调用View.OnClickListener一样简单

    mBleScanner = new BleScanner(activity, new SimpleScanCallback() {
        @Override
        public void onBleScan(BluetoothDevice bluetoothDevice, int rssi, byte[] scanRecord) {
           //device :设备信息 mac、name
           //rssi :可以认为是蓝牙信号值
           //scanRecord :广播报文
           doSomething();
        }

        @Override
        public void onBleScanFailed(BleScanState scanState) {
           //scanState 扫描错误码
        }
    });
    //开始扫描(一直扫描)
    mBleScanner.startBleScan();
    //开始扫描并设置扫描时长(超时回调onBleScanFailed())
    mBleScanner.onStartBleScan(long timeoutMillis);
    //停止扫描
    mBleScanneronStopBleScan();

二、连接蓝牙设备

1.扫描设备成功后,bluetoothDevice可以直接连接设备(如果不需要后续操作的话)
  BluetoothDevice.connectGatt(Context context, boolean autoConnect, new BluetoothGattCallback() {
      @Override
      public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
          BleLog.i(TAG, "onConnectionStateChange: State = " + BleUtils.getBleConnectStatus(status)
                  + " newState = " + BleUtils.getBleConnectStatus(newState));

          if (newState == BluetoothProfile.STATE_CONNECTED) {
              //连接成功
          } else {
              //连接成功失败

          }
      }
  });
2.扫描设备成功后,连接设备需要后续的操作,通过下面方式)
  mBleHelper = new AppBluetoothHelper(Context context, BleCallback bleCallback);
  mBleHelper.bindService(new AppBluetoothHelper.OnBindListener() {
      @Override
      public void onServiceConnected() {
          connectDevice(String deviceMac);
      }
  });
  public void connectDevice(final String deviceMac) {
    mBleHelper.connectDevice(deviceMac, new ConnectCallback() {
        @Override
        public void onConnectSuccess() {
            mBleHelper.mConnCallback = null;//不再需要回调了
            
        }

        @Override
        public void onConnectFailed(ConnectError error) {

        }
    });
  }

三、蓝牙设备通信

依然像调用View.OnClickListener一样简单,发送消息之后响应回调回来
目前只实现了常用的三个操作

    //写~
    mBleHelper.writeCharacteristic(UUID serviceUUID, UUID CharacteristicUUID, byte[] values)
    //开关notify
    mBleHelper.updateCharacteristicNotification(UUID serviceUUID, UUID characteristicUUID, UUID descriptorUUID, boolean enable)
    //读取特征值
    mBleHelper.readFromCharacteristic(UUID serviceUUID, UUID CharacteristicUUID) 
    //bleCallback中接收蓝牙响应~可以通过uuid来区分消息
    BluetoothHelper(Context context, BleCallback bleCallback)
    //Callback triggered as a result of a remote characteristic notification.
    //BluetoothGattCallback#onCharacteristicChanged --> onCharacteristicNotification
    public void onCharacteristicNotification(UUID uuid, byte[] data) {

    }

    //Callback reporting the result of a characteristic read operation.
    //BluetoothGattCallback#onCharacteristicChanged
    public void onCharacteristicRead(UUID uuid, byte[] data) {

    }

    //Callback indicating the result of a characteristic write operation.
    //BluetoothGattCallback#onCharacteristicWrite
    public void onCharacteristicWrite(UUID uuid, int status) {

    }

搞定收工

GitHub地址:https://github.com/captain-miao/bleYan
apk下载地址:http://fir.im/bleYan