– layout: post # 使用的布局(不需要改) title: Android Bluetooth Low Energy 基础 # 标题 subtitle: #副标题 date: 2017-01-11 # 时间 author: PanMin # 作者 header-img: img/post-bg-2015.jpg #这篇文章标题背景图片 catalog: true # 是否归档 tags: #标签 - Android ble —
1、关于Android平台的BLE
蓝牙4.0于2010年发布,相对于上个版本3.0,它的特点是更省电、成本低 延迟低等特点,现在最新的蓝牙协议是2013年底发布的蓝牙4.1,蓝牙4.1在4.0 基础上进行升级,使得可穿戴设备的批量数据传输速度更高。Android是从4.3 才开始提供BLE API,这也就限定了BLE的应用只能运行在Android 4.3及其以上 的系统。在Android平台上的蓝牙4.0主要有两种工作模式:经典蓝牙(classic bluetooth) 、低功耗蓝牙(bluetooth low energy,缩写为BLE)
2、角色与职责
当一个Android设备与一个BLE设备进行交互通信时,主要存在以下两种关系 中心设备与外围设备:中心设备扮演扫描的角色,寻找外围设备的广播消息。Android设备 作为中心设备,与之连接通信的设备作为外围设备。
GATT服务器与GATT客户端:这种关系决定了当连接建立后两个设备如何通信。
注:目前Android系统提供的API使得Android设备只能作为中心设备
3、组成部分
BLE分为三个部分Service、Characteristic、Descriptor,每个部分都拥有不同的 UUID来标识。一个BLE设备可以拥有多个Service,一个Service可以包含多个Characteristic, 一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。 通信数据一般存储在Characteristic内,目前一个Characteristic中存储的数据最大为20 byte。 与Characteristic相关的权限字段主要有READ、WRITE、WRITE_NO_RESPONSE、NOTIFY。 Characteristic具有的权限属性可以有一个或者多个。
4、核心代码
private BluetoothAdapter mBtAdapter = null;
private BluetoothGatt mBtGatt = null;
private int mState = 0;
private Context mContext;
private BluetoothGattCharacteristic mWriteCharacteristic = null;
private BluetoothGattCharacteristic mReadCharacteristric = null;
private final String TAG = "BLE_Demo";
// 设备连接状态
private final int CONNECTED = 0x01;
private final int DISCONNECTED = 0x02;
private final int CONNECTTING = 0x03;
// 读写相关的Service、Characteristic的UUID
public static final UUID TRANSFER_SERVICE_READ = UUID.fromString("34567817-2432-5678-1235-3c1d5ab44e17");
public static final UUID TRANSFER_SERVICE_WRITE = UUID.fromString("34567817-2432-5678-1235-3c1d5ab44e18");
public static final UUID TRANSFER_CHARACTERISTIC_READ = UUID.fromString("23487654-5678-1235-2432-3c1d5ab44e94");
public static final UUID TRANSFER_CHARACTERISTIC_WRITE = UUID.fromString("23487654-5678-1235-2432-3c1d5ab44e93");
// BLE设备连接通信过程中回调
private BluetoothGattCallback mBtGattCallback = new BluetoothGattCallback() {
// 连接状态发生改变时的回调
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState) {
if (status == BluetoothGatt.GATT_SUCCESS) {
mState = CONNECTED;
Log.d(TAG, "connected OK");
mBtGatt.discoverServices();
} else if (newState == BluetoothGatt.GATT_FAILURE) {
mState = DISCONNECTED;
Log.d(TAG, "connect failed");
}
}
// 远端设备中的服务可用时的回调
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
BluetoothGattService btGattWriteService = mBtGatt
.getService(TRANSFER_SERVICE_WRITE);
BluetoothGattService btGattReadService = mBtGatt
.getService(TRANSFER_SERVICE_READ);
if (btGattWriteService != null) {
mWriteCharacteristic = btGattWriteService
.getCharacteristic(TRANSFER_CHARACTERISTIC_WRITE);
}
if (btGattReadService != null) {
mReadCharacteristric = btGattReadService
.getCharacteristic(TRANSFER_CHARACTERISTIC_READ);
if (mReadCharacteristric != null) {
mBtGatt.readCharacteristic(mReadCharacteristric);
}
}
}
}
// 某Characteristic的状态为可读时的回调
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
readCharacterisricValue(characteristic);
// 订阅远端设备的characteristic,
// 当此characteristic发生改变时当回调mBtGattCallback中的onCharacteristicChanged方法
mBtGatt.setCharacteristicNotification(mReadCharacteristric,
true);
BluetoothGattDescriptor descriptor = mReadCharacteristric
.getDescriptor(UUID
.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (descriptor != null) {
byte[] val = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
descriptor.setValue(val);
mBtGatt.writeDescriptor(descriptor);
}
}
}
// 写入Characteristic成功与否的回调
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
switch (status) {
case BluetoothGatt.GATT_SUCCESS:
Log.d(TAG, "write data success");
break;// 写入成功
case BluetoothGatt.GATT_FAILURE:
Log.d(TAG, "write data failed");
break;// 写入失败
case BluetoothGatt.GATT_WRITE_NOT_PERMITTED:
Log.d(TAG, "write not permitted");
break;// 没有写入的权限
}
}
// 订阅了远端设备的Characteristic信息后,
// 当远端设备的Characteristic信息发生改变后,回调此方法
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
readCharacterisricValue(characteristic);
}
};
/**
* 读取BluetoothGattCharacteristic中的数据
*
* @param characteristic
*/
private void readCharacterisricValue(
BluetoothGattCharacteristic characteristic) {
byte[] data = characteristic.getValue();
StringBuffer buffer = new StringBuffer("0x");
int i;
for (byte b : data) {
i = b & 0xff;
buffer.append(Integer.toHexString(i));
}
Log.d(TAG, "read data:" + buffer.toString());
}
/**
* 与指定的设备建立连接
*
* @param device
*/
public void connect(BluetoothDevice device) {
mBtGatt = device.connectGatt(mContext, false, mBtGattCallback);
mState = CONNECTTING;
}
/**
* 初始化
*
* @param context
* @return 如果初始化成功则返回true
*/
public boolean init(Context context) {
BluetoothManager btMrg = (BluetoothManager) context
.getSystemService(Context.BLUETOOTH_SERVICE);
if (btMrg == null)
return false;
mBtAdapter = btMrg.getAdapter();
if (mBtAdapter == null)
return false;
mContext = context;
return true;
}
// BLE设备搜索过程中的回调,在此可以根据外围设备广播的消息来对设备进行过滤
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
ArrayUtils.reverse(scanRecord);// 数组反转
// 将Byte数组的数据以十六进制表示并拼接成字符串
StringBuffer str = new StringBuffer();
int i = 0;
for (byte b : scanRecord) {
i = (b & 0xff);
str.append(Integer.toHexString(i));
}
String discoveryServceID = str.toString();
Log.d(TAG, device.getName() + " scanRecord:\n" + discoveryServceID);
// 查询是否含有指定的Service UUID信息
if (discoveryServceID.indexOf(TRANSFER_SERVICE_WRITE.toString()
.replace("-", "")) != -1) {
Log.d(TAG, device.getName() + " has available service UUID");
// 在这是处理匹配的设备……
}
}
};
/**
* 开始BLE设备扫描
*/
public void startScan() {
mBtAdapter.startLeScan(mLeScanCallback);
}
/**
* 停止BLE设备扫描
*/
public void stopScan() {
mBtAdapter.stopLeScan(mLeScanCallback);
}
/**
* 发送数据
*
* @param data
* 待发送的数据,最大长度为20
*/
private void sendData(byte[] data) {
if (data != null && data.length > 0 && data.length < 21) {
if (mWriteCharacteristic.setValue(data)
&& mBtGatt.writeCharacteristic(mWriteCharacteristic)) {
Log.d(TAG, "send data OK");
}
}
}
Android Bluetooth Low Energy 数据的写入与读取
1 、启动ble设备连接的方法首先要得到一个BluetoothDevice, 这个可以通过,android搜索一配对或者为配对的蓝牙设备获取得到BluetoothDevice的对象,也可以通过搜索ble设备去得到一个BluetoothDevice的对象(下面会仔细说索搜ble设备)需要注意的是,当一个ble设备作为一个蓝牙设备连接到了android 设备上的话,我们通过搜索ble设备的api是搜索不到的。
2、得到一个蓝牙已配对设备
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.startDiscovery();
Set<BluetoothDevice> bledevices = mBluetoothAdapter.getBondedDevices();
if (bledevices.size() > 0)
{
for (BluetoothDevice bluetoothDevice : bledevices)
{
if (bluetoothDevice.getAddress().toString()
.equals(BLUETOOTH_DEVICE_MAC))
{
Log.v(TAG, "find with old: " + bluetoothDevice.getAddress());
bluetoothDevice.connectGatt(getActivity(), false,
mGattCallback);
}
}
}
3、如何索搜ble设备 :
使用BluetoothAdapter 的 startLeScan 方法,
startLeScan 的参数是一个回调。
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback()
{
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord)
{
Log.v(TAG, "device: " + device);
}
};
在这个回调里可以索搜到BLE设备
4、使用索搜到的设备连接 当我们搜索到设备的时候, 可以使用
device.connectGatt(getActivity(), false, mGattCallback);
进行连接,其中 mGattCallback 也是个回调方法
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback()
{
// 设备发生读操作时的回调
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status)
{
Log.v(TAG, "onCharacteristicRead: " + characteristic.getUuid());
}
// 设备发生写操作是的回调
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status)
{
if (BluetoothGatt.GATT_SUCCESS == status)
{
Log.v(TAG, "onCharacteristicWrite: " + characteristic.getUuid());
}
super.onCharacteristicWrite(gatt, characteristic, status);
}
// 设备连接状态发生更改时的回调
// 我们要在这里进行discoverServices()
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState)
{
gatt.discoverServices();
super.onConnectionStateChange(gatt, status, newState);
}
// 当BLE设备中的服务被找到时,回调如下方法
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status)
{
Log.v(TAG, "onServicesDiscovered: " + gatt.getServices().size());
super.onServicesDiscovered(gatt, status);
}
};
5、重点:数据的IO 无论数据或是写,都需要
BluetoothGattService mSVC = gatt.getService(UUID
.fromString("0000dfb0-0000-1000-8000-00805f9b34fb"));
BluetoothGattCharacteristic mCH = mSVC.getCharacteristic(UUID
.fromString("0000dfb1-0000-1000-8000-00805f9b34fb"));
得到一个BluetoothGattCharacteristic , 如上, 我们同过一个指定uuid的方式,在
public void onServicesDiscovered() 方法中得到gatt ,调用gatt的读操作
gatt.readCharacteristic(mCH);
执行这部操作之后, 我们会在 onCharacteristicRead 方法回掉这个 Characteristic 的所带有的value
写操作则是
mCH.setValue(GAME_MODE);
gatt.writeCharacteristic(mCH);
同理,在 onCharacteristicWrite 方法中可以获得操作的提示。 Characteristic 里的数据,结果也正确, 但是无法语言写入是否正确,目前执行了write的操作,系统显示写入正常, 但是看不到相应的变化
写数据的时候将值这样写 ch.setValue(new byte[] { (byte) 0x00, (byte) 0xff });
import java.util.Set;
import java.util.UUID;
import youten.redo.ble.util.BleUtil;
import youten.redo.ble.util.BleUuid;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.Toast;
public class DeviceActivity extends Activity implements View.OnClickListener
{
private static final String TAG = "BLEDevice";
public static final String EXTRA_BLUETOOTH_DEVICE = "BT_DEVICE";
private BluetoothAdapter mBTAdapter;
private BluetoothDevice mDevice;
private BluetoothGatt mConnGatt;
private int mStatus;
private Button mReadManufacturerNameButton;
private Button mReadSerialNumberButton;
private Button mWriteAlertLevelButton;
private final BluetoothGattCallback mGattcallback = new BluetoothGattCallback()
{
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,
int newState)
{
if (newState == BluetoothProfile.STATE_CONNECTED)
{
mStatus = newState;
Log.v(TAG, "connected and discoverServices");
mConnGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED)
{
mStatus = newState;
runOnUiThread(new Runnable()
{
public void run()
{
mReadManufacturerNameButton.setEnabled(false);
mReadSerialNumberButton.setEnabled(false);
mWriteAlertLevelButton.setEnabled(false);
};
});
}
};
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status)
{
Log.v(TAG, "is have services");
for (BluetoothGattService service : gatt.getServices())
{
if ((service == null) || (service.getUuid() == null))
{
continue;
}
if (BleUuid.SERVICE_DEVICE_INFORMATION.equalsIgnoreCase(service
.getUuid().toString()))
{
mReadManufacturerNameButton
.setTag(service.getCharacteristic(UUID
.fromString(BleUuid.CHAR_MANUFACTURER_NAME_STRING)));
mReadSerialNumberButton
.setTag(service.getCharacteristic(UUID
.fromString(BleUuid.CHAR_SERIAL_NUMBEAR_STRING)));
runOnUiThread(new Runnable()
{
public void run()
{
mReadManufacturerNameButton.setEnabled(true);
mReadSerialNumberButton.setEnabled(true);
};
});
}
if (BleUuid.SERVICE_IMMEDIATE_ALERT.equalsIgnoreCase(service
.getUuid().toString()))
{
runOnUiThread(new Runnable()
{
public void run()
{
mWriteAlertLevelButton.setEnabled(true);
};
});
mWriteAlertLevelButton.setTag(service
.getCharacteristic(UUID
.fromString(BleUuid.CHAR_ALERT_LEVEL)));
}
}
runOnUiThread(new Runnable()
{
public void run()
{
setProgressBarIndeterminateVisibility(false);
};
});
};
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status)
{
if (status == BluetoothGatt.GATT_SUCCESS)
{
byte[] bytes = characteristic.getValue();
for (byte b : bytes)
{
Log.v("", "------characteristic------" + b);
}
if (BleUuid.CHAR_MANUFACTURER_NAME_STRING
.equalsIgnoreCase(characteristic.getUuid().toString()))
{
final String name = characteristic.getStringValue(0);
runOnUiThread(new Runnable()
{
public void run()
{
mReadManufacturerNameButton.setText(name);
setProgressBarIndeterminateVisibility(false);
};
});
} else if (BleUuid.CHAR_SERIAL_NUMBEAR_STRING
.equalsIgnoreCase(characteristic.getUuid().toString()))
{
final String name = characteristic.getStringValue(0);
runOnUiThread(new Runnable()
{
public void run()
{
mReadSerialNumberButton.setText(name);
setProgressBarIndeterminateVisibility(false);
};
});
}
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status)
{
if (BluetoothGatt.GATT_SUCCESS == status)
{
Log.v(TAG,
"write - characteristic: " + characteristic.getUuid());
Log.v(TAG, "write - service "
+ characteristic.getService().getUuid());
}
// 再读取一次, 验证一下值是不是对的
gatt.readCharacteristic(characteristic);
runOnUiThread(new Runnable()
{
public void run()
{
setProgressBarIndeterminateVisibility(false);
};
});
};
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.activity_device);
// state
mStatus = BluetoothProfile.STATE_DISCONNECTED;
mReadManufacturerNameButton = (Button) findViewById(R.id.read_manufacturer_name_button);
mReadManufacturerNameButton.setOnClickListener(this);
mReadSerialNumberButton = (Button) findViewById(R.id.read_serial_number_button);
mReadSerialNumberButton.setOnClickListener(this);
mWriteAlertLevelButton = (Button) findViewById(R.id.write_alert_level_button);
mWriteAlertLevelButton.setOnClickListener(this);
}
@Override
protected void onResume()
{
super.onResume();
init();
}
@Override
protected void onDestroy()
{
super.onDestroy();
if (mConnGatt != null)
{
if ((mStatus != BluetoothProfile.STATE_DISCONNECTING)
&& (mStatus != BluetoothProfile.STATE_DISCONNECTED))
{
mConnGatt.disconnect();
}
mConnGatt.close();
mConnGatt = null;
}
}
@Override
public void onClick(View v)
{
if (v.getId() == R.id.read_manufacturer_name_button)
{
if ((v.getTag() != null)
&& (v.getTag() instanceof BluetoothGattCharacteristic))
{
BluetoothGattCharacteristic ch = (BluetoothGattCharacteristic) v
.getTag();
if (mConnGatt.readCharacteristic(ch))
{
setProgressBarIndeterminateVisibility(true);
}
}
} else if (v.getId() == R.id.read_serial_number_button)
{
if ((v.getTag() != null)
&& (v.getTag() instanceof BluetoothGattCharacteristic))
{
BluetoothGattCharacteristic ch = (BluetoothGattCharacteristic) v
.getTag();
if (mConnGatt.readCharacteristic(ch))
{
setProgressBarIndeterminateVisibility(true);
}
}
} else if (v.getId() == R.id.write_alert_level_button)
{
if ((v.getTag() != null)
&& (v.getTag() instanceof BluetoothGattCharacteristic))
{
BluetoothGattCharacteristic ch = (BluetoothGattCharacteristic) v
.getTag();
ch.setValue(new byte[] { (byte) 0x00, (byte) 0xff });
if (mConnGatt.writeCharacteristic(ch))
{
Log.v(TAG, "1st wirte had readly");
setProgressBarIndeterminateVisibility(true);
}
}
}
}
private void init()
{
// BLE check
if (!BleUtil.isBLESupported(this))
{
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT)
.show();
finish();
return;
}
// BT check
BluetoothManager manager = BleUtil.getManager(this);
if (manager != null)
{
mBTAdapter = manager.getAdapter();
}
if (mBTAdapter == null)
{
Toast.makeText(this, R.string.bt_unavailable, Toast.LENGTH_SHORT)
.show();
finish();
return;
}
// check BluetoothDevice
if (mDevice == null)
{
mDevice = getBTDeviceExtra();
if (mDevice == null)
{
finish();
return;
}
}
// button disable
mReadManufacturerNameButton.setEnabled(false);
mReadSerialNumberButton.setEnabled(false);
mWriteAlertLevelButton.setEnabled(false);
// connect to Gatt
if ((mConnGatt == null)
&& (mStatus == BluetoothProfile.STATE_DISCONNECTED))
{
Log.v(TAG, "try to connect with connectGatt");
// try to connect
mConnGatt = mDevice.connectGatt(this, false, mGattcallback);
mStatus = BluetoothProfile.STATE_CONNECTING;
} else
{
if (mConnGatt != null)
{
// re-connect and re-discover Services
Log.v(TAG, "try to connect with connect...");
mConnGatt.connect();
mConnGatt.discoverServices();
} else
{
Log.e(TAG, "state error");
finish();
return;
}
}
setProgressBarIndeterminateVisibility(true);
}
private BluetoothAdapter mBluetoothAdapter;
// 读取一个固定的mac 地址, 进行验证
private static final String BLUETOOTH_DEVICE_MAC = "D0:06:8E:75:56:16";
private BluetoothDevice getBTDeviceExtra()
{
BluetoothDevice bleDevice = null;
Intent intent = getIntent();
if (intent == null)
{
return null;
}
Bundle extras = intent.getExtras();
if (extras == null)
{
return null;
}
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.startDiscovery();
Set<BluetoothDevice> bledevices = mBluetoothAdapter.getBondedDevices();
if (bledevices.size() > 0)
{
for (BluetoothDevice bluetoothDevice : bledevices)
{
Log.v(TAG,
"bletoothdevice mac address: "
+ bluetoothDevice.getAddress());
if (bluetoothDevice.getAddress().toString()
.equals(BLUETOOTH_DEVICE_MAC))
{
bleDevice = bluetoothDevice;
}
}
}
return bleDevice;
}
}