科普
https://zh.wikipedia.org/wiki/%E8%97%8D%E7%89%99
蓝牙(英语:Bluetooth),一种无线通讯技术标准,用来让固定与移动设备,在短距离间交换资料,以形成个人局域网(PAN)。其使用短波特高频(UHF)无线电波,经由 2.4 至 2.485 GHz 的 ISM(工业、科学、医疗)频段来进行通信。1994 年由电信商爱立信(Ericsson)发展出这个技术。它最初的设计,是希望创建一个 RS-232 数据线的无线通信替代版本。它能够链接多个设备,克服同步的问题。
SIG
蓝牙技术联盟(英语:Bluetooth Special Interest Group,缩写为SIG)拥有蓝牙的商标,负责制定蓝牙规范、认证制造厂商,授权他们使用蓝牙技术与蓝牙标志,但本身不负责蓝牙设备的设计、生产及贩售。
类型
Classic Bluetooth、Bluetooth Low Energy(蓝牙 4.0 及更高版本)
制式
分 Single mode 与 Dual mode。 Single mode 只能与 BT4.0 互相传输无法向下兼容(与 3.0/2.1/2.0 无法相通);Dual mode 可以向下兼容,可与 BT4.0 传输也可以跟 3.0/2.1/2.0 传输
选择
经典蓝牙:蓝牙最初的设计意图,是打电话放音乐。3.0 版本以下的蓝牙,都称为“经典蓝牙”。功耗高、传输数据量大、传输距离只有 10 米。适用于传输音视频等数据量大或需要连续宽带链接的鼠标和其他设备的应用场合。
低功耗蓝牙:就是 BLE,通常说的蓝牙 4.0(及以上版本)。低功耗,数据量小,距离 50 米左右。使用于电池供电、连手机 APP 读取设备信息等应用场合。
编程
Bluetooth Classic vs. Bluetooth Low Energy (BLE) on Android – Hints & Implementation Steps Android-经典蓝牙(BT)-建立长连接传输短消息和文件 BLE与蓝牙科普 Android蓝牙健康设备开发:Health Device Profile(HDP) Android蓝牙开发—经典蓝牙和BLE(低功耗)蓝牙的区别 Android低功耗蓝牙(BLE)开发的一点感受 【经验】低功耗蓝牙模块如何实现数据传输?
Classic Bluetooth 和 Bluetooth Low Energy 开发有很大的区别。
连接: Classic Bluetooth 建立连接的方式实际上就是 Socket 的连接的建立,利用搜索找到的 BluetoothDevice,调用其方法 createRfcommSocketToServiceRecord(UUID)。最后,使用获取到的 BluetoothDevice 调用其方法 connect()。
Bluetooth Low Energy 建立连接的方式类似于数据库的连接。通过 BluetoothAdapter 的 getRemoteDevice(address) 方法获取相应 BLE 从设备的 BluetoothDevice,其中的 address 为目标蓝牙设备 MAC 地址。然后通过此 BluetoothDevice 的 connectGatt(this, false, mGattCallback) 方法获取设备连接以及返回属性句柄来访问 Gatt 数据库。此时的连接,只能够进行监听,也就是获取到当前BLE从设备广播出来的数据。
读写: Classic Bluetooth 使用 BluetoothSocket 的 getOutputStream() 方法获取输出流写入需要发送的数据,调用 BluetoothSocket 的 getInputStream() 方法获取输入流读取。
Bluetooth Low Energy 想要实现主设备对从设备的数据发送,则需要直接读取获取到的从设备的 Characteristic。
蓝牙权限
BLUETOOTH :(必需),例如请求连接、接受连接和传输数据。 BLUETOOTH_ADMIN :(可选),如果您希望您的应用程序启动设备发现或操作蓝牙设置、创建套接字连接,除了 BLUETOOTH 权限之外,您还必须声明此权限。大多数应用程序作为连接的主动方需要此权限仅用于发现本地蓝牙设备。 ACCESS_FINE_LOCATION :(必需),因为蓝牙扫描需要收集有关用户位置的信息。该信息可能来自用户自己的设备,以及在商店和交通设施等位置使用的蓝牙信标。
由于 ACCESS_FINE_LOCATION 是 危险权限,你需要在清单中声明它并 在运行时请求此权限。
Note: Android 9(API 级别 28)或更低版本,需要声明 ACCESS_COARSE_LOCATION 而不是 ACCESS_FINE_LOCATION 权限。 Note: Android 10 及更高版本,需要拥有 ACCESS_BACKGROUND_LOCATION 权限才能发现蓝牙设备。有关此要求的更多信息,请参阅 后台访问位置。 使用 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) 条件判断该版本。 Note: Android 8.0(API 级别 26)及更高版本,可以使用 CompanionDeviceManager 对附近的配套设备执行扫描,而无需位置权限。有关此选项的更多信息,请参阅 配套设备配对。
如果您希望您的应用程序启动设备发现或操作蓝牙设置,除了 BLUETOOTH 权限之外,您还必须声明 BLUETOOTH_ADMIN 权限。
Classic Bluetooth
https://developer.android.com/guide/topics/connectivity/bluetooth https://developer.android.com/guide/topics/connectivity/bluetooth?hl=zh-cn
Bluetooth Low Energy
https://developer.android.com/guide/topics/connectivity/bluetooth/ble-overview https://developer.android.com/guide/topics/connectivity/bluetooth-le?hl=zh-cn Android BluetoothLeGatt Sample
术语
Generic Attribute Profile (GATT):通用属性配置文件 是解释如何在 BLE 链路发送和接收“属性”短数据段的通用规范。当前所有的 BLE 应用配置文件都是基于 GATT 通过“属性”进行交流。一个设备可以实现多个服务的配置文件。例如,一台设备可能包含心率监测仪和电池电量检测器。 Attribute Protocol (ATT):GATT 建立在属性协议 (ATT) 之上,二者的关系也被称为 GATT/ATT。该协议为在 BLE 设备上运行进行优化,使用尽可能少的字节。每个属性均由通用唯一标识符 (UUID) 进行唯一标识,按照 Service - Characteristic 的格式传输。 Characteristic:特征 包含一个特征值以及 0-n 个描述此特征值的 Descriptor,相当于一个类。 Descriptor:描述符 是已定义的描述特征值的属性。例如,描述符可指定特征值的可接受范围或特定于特征值的度量单位。 Service:服务 是特征的集合。例如,在名为“心率监测器”的服务中包括“心率测量”等特征。在 bluetooth.org 上可查看现有的 GATT 配置文件及服务的列表。
角色 & 职能
在 BLE 连接中,中央(主)设备进行扫描、寻找广播;外围(从)设备发出广播。 在 BLE 数据传输中,发送数据的设备充当 Gatt 服务器,接收数据的设备充当 Gatt 客户端。
查找 BLE 设备
使用 startLeScan() 方法,将 BluetoothAdapter.LeScanCallback 作为参数。必须实现此回调。扫描非常耗电,要设置扫描时间限制;找到所需设备后,立即停止扫描。
以下代码段展示如何启动和停止扫描:
public class DeviceScanActivity extends ListActivity {
private BluetoothAdapter bluetoothAdapter;
private boolean mScanning;
private Handler handler;
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
handler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
bluetoothAdapter.stopLeScan(leScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
bluetoothAdapter.startLeScan(leScanCallback);
} else {
mScanning = false;
bluetoothAdapter.stopLeScan(leScanCallback);
}
...
}
...
}
Note: The BluetoothLeScanner is only available from the BluetoothAdapter if Bluetooth is currently enabled on the device. If Bluetooth is not enabled, then getBluetoothLeScanner() returns null.
以下代码示例是 ScanCallback 的实现,它是用于传递 BLE 扫描结果的接口。找到结果后,会将它们添加到 DeviceScanActivity 中的列表适配器中以显示给用户。
private LeDeviceListAdapter leDeviceListAdapter = new LeDeviceListAdapter();
private ScanCallback leScanCallback =
new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
leDeviceListAdapter.addDevice(result.getDevice());
leDeviceListAdapter.notifyDataSetChanged();
}
};
Note: You can only scan for Bluetooth LE devices or scan for classic Bluetooth devices, as described in Bluetooth overview. You cannot scan for both Bluetooth LE and classic devices at the same time.
连接设备上的 GATT 服务器
绑定服务
在本例中,提供一个 Activity (DeviceControlActivity) 来连接、显示设备及服务数据。真正通过 API 与 BLE 设备交互的是名为 BluetoothLeService 的 Service:
class BluetoothLeService extends Service {
private Binder binder = new LocalBinder();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}
class LocalBinder extends Binder {
public BluetoothLeService getService() {
return BluetoothLeService.this;
}
}
}
class DeviceControlActivity extends AppCompatActivity {
private BluetoothLeService bluetoothService;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bluetoothService = ((LocalBinder) service).getService();
if (bluetoothService != null) {
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bluetoothService = null;
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gatt_services_characteristics);
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
bindService(gattServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
}
蓝牙设置
是否支持蓝牙可用 BluetoothAdapter 判断,如果 getDefaultAdapter() 返回 null,则该设备不支持蓝牙。此逻辑包装在一个 initialize() 函数中,Activity 在其 ServiceConnection 实现中调用此函数。
class BluetoothLeService extends Service {
public static final String TAG = "BluetoothLeService";
private BluetoothAdapter bluetoothAdapter;
public boolean initialize() {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
...
}
class DeviceControlsActivity extends AppCompatActivity {
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bluetoothService = ((LocalBinder) service).getService();
if (bluetoothService != null) {
if (!bluetoothService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bluetoothService = null;
}
};
...
}
不支持蓝牙时退出应用,做好用户提示。
BluetoothAdapter 的 isEnabled() 判断蓝牙是否开启。如果未开启,可以主动通过代码引导用户开启,或者被动监听蓝牙状态的广播,确保之后的操作中蓝牙可以使用。
- 以编程方式启用它 BluetoothAdapter.getDefaultAdapter().enable()
- startActivityForResult() 传递 ACTION_REQUEST_ENABLE 意图可显示对话框,通过系统设置开启蓝牙(无需停止您的应用)。
Figure 1: The enabling Bluetooth dialog.
- 上述方法还需要在 onActivityResult() 中检查返回值,直接 startActivity() 启动可检测性 则无需此步骤。
Figure 2: The enabling discoverability dialog.
注册广播监听器添加 ACTION_STATE_CHANGED 意图,监听蓝牙启闭状态。
连接到设备
一旦 BluetoothService 被初始化,它就可以连接到 BLE 设备。活动需要将 BLE 设备地址发送到服务,以便它可以启动连接。该服务将首先调用 BluetoothAdapter 上的 getRemoteDevice() 来访问设备。如果适配器无法找到具有该地址的设备,getRemoteDevice() 将抛出 IllegalArgumentException。
有效的蓝牙硬件地址必须大写,格式如“00:11:22:33:AA:BB”。BluetoothAdapter#checkBluetoothAddress(String) 可用于验证蓝牙地址。
public boolean connect(final String address) {
if (bluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
try {
final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
} catch (IllegalArgumentException exception) {
Log.w(TAG, "Device not found with provided address.");
return false;
}
}
一旦服务被初始化,DeviceControlActivity 就会调用这个 connect() 函数。在以下示例中,设备地址作为额外意图由 DeviceScanActivity 传递过来。
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bluetoothService = ((LocalBinder) service).getService();
if (bluetoothService != null) {
if (!bluetoothService.initialize()) {
Log.e(TAG, "Unable to initialize Bluetooth");
finish();
}
bluetoothService.connect(deviceAddress);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bluetoothService = null;
}
};
连接到 GATT 服务
一旦服务连接到设备,则需要进一步连接到 BLE 设备上的 GATT 服务器。使用 connectGatt() 方法。此方法采用三个参数:一个 Context 对象、autoConnect(布尔值,指示是否在可用时自动连接到 BLE 设备),以及对 BluetoothGattCallback 的引用。BluetoothGattCallback 来接收有关连接状态、服务发现、特征读取和特征通知的通知。该方法返回 BluetoothGatt 实例,然后您可使用该实例执行 GATT 客户端操作,例如,不再需要时关闭连接。。
在此示例中,应用程序直接连接到 BLE 设备,因此为 autoConnect 传递 false。
class BluetoothService extends Service {
...
private BluetoothGatt bluetoothGatt;
...
public boolean connect(final String address) {
if (bluetoothAdapter == null || address == null) {
Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
return false;
}
try {
final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address);
bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback);
return true;
} catch (IllegalArgumentException exception) {
Log.w(TAG, "Device not found with provided address. Unable to connect.");
return false;
}
}
}
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
}
}
};
状态广播更新
该服务声明了一个广播新状态的函数,接收动作字符串参数形成 Intent 向系统发送广播。
private void broadcastUpdate(final String action) {
final Intent intent = new Intent(action);
sendBroadcast(intent);
}
在 BluetoothGattCallback 中使用以发送有关与 GATT 服务器的连接状态的信息。
class BluetoothService extends Service {
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";
private static final int STATE_DISCONNECTED = 0;p
private static final int STATE_CONNECTED = 2;
private int connectionState;
...
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
connectionState = STATE_CONNECTED;
broadcastUpdate(ACTION_GATT_CONNECTED);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
connectionState = STATE_DISCONNECTED;
broadcastUpdate(ACTION_GATT_DISCONNECTED);
}
}
};
…
}
Activity 更新
通过侦听来自服务的事件,活动能够根据与 BLE 设备的当前连接状态更新用户界面。
class DeviceControlsActivity extends AppCompatActivity {
...
private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
connected = true;
updateConnectionState(R.string.connected);
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
connected = false;
updateConnectionState(R.string.disconnected);
}
}
};
@Override
protected void onResume() {
super.onResume();
registerReceiver(gattUpdateReceiver, makeGattUpdateIntentFilter());
if (bluetoothService != null) {
final boolean result = bluetoothService.connect(deviceAddress);
Log.d(TAG, "Connect request result=" + result);
}
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(gattUpdateReceiver);
}
private static IntentFilter makeGattUpdateIntentFilter() {
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
return intentFilter;
}
}
在传输 BLE 数据中,BroadcastReceiver 还用于通知寻找服务以及读取特征数据的结果。
关闭 GATT 连接
当活动与服务解除绑定时,连接将关闭以避免耗尽设备电池。
class BluetoothService extends Service {
...
@Override
public boolean onUnbind(Intent intent) {
close();
return super.onUnbind(intent);
}
private void close() {
if (bluetoothGatt == null) {
Return;
}
bluetoothGatt.close();
bluetoothGatt = null;
}
}
传输 BLE 数据
连接到 BLE GATT 服务器后,您可以使用该连接来找出设备上可用的服务、从设备查询数据以及在某个 GATT 特性发生变化时请求通知。
寻找服务
连接到 BLE 设备上的 GATT 服务器后要做的第一件事就是执行服务发现。在以下示例中,一旦服务成功连接到设备,discoverServices() 函数会从 BLE 设备查询信息。
该服务需要覆盖 BluetoothGattCallback 中的 onServicesDiscovered() 函数。当设备报告其可用服务时调用此函数。
class BluetoothService extends Service {
public final static String ACTION_GATT_SERVICES_DISCOVERED =
"com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
...
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
connectionState = STATE_CONNECTED;
broadcastUpdate(ACTION_GATT_CONNECTED);
bluetoothGatt.discoverServices();
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
connectionState = STATE_DISCONNECTED;
broadcastUpdate(ACTION_GATT_DISCONNECTED);
}
}
@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);
}
}
};
}
该服务使用广播来通知活动。一旦发现了服务,服务就可以调用 getServices() 来获取报告的数据。
class BluetoothService extends Service {
...
public List<BluetoothGattService> getSupportedGattServices() {
if (bluetoothGatt == null) return null;
return bluetoothGatt.getServices();
}
}
Activity 可以在收到广播 Intent 时调用此函数,表明服务发现已经完成。
class DeviceControlsActivity extends AppCompatActivity {
...
private final BroadcastReceiver gattUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
connected = true;
updateConnectionState(R.string.connected);
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
connected = false;
updateConnectionState(R.string.disconnected);
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
displayGattServices(bluetoothService.getSupportedGattServices());
}
}
};
}
读取 BLE 特征
一旦您的应用程序连接到 GATT 服务器并发现服务,它就可以在支持的情况下读取和写入属性。例如,以下代码段遍历服务器的服务和特征并将它们显示在 UI 中:
public class DeviceControlActivity extends Activity {
...
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>>();
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>();
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 服务器提供您可以从设备读取的特性列表。为了查询数据,调用BluetoothGatt上的readCharacteristic()函数,传入你要读取的BluetoothGattCharacteristic。
class BluetoothService extends Service {
...
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (bluetoothGatt == null) {
Log.w(TAG, "BluetoothGatt not initialized");
return;
}
bluetoothGatt.readCharacteristic(characteristic);
}
}
在这个例子中,服务实现了一个函数来调用 readCharacteristic()。这是一个异步调用。结果被发送到BluetoothGattCallback 函数onCharacteristicRead()。
class BluetoothService extends Service {
...
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
...
@Override
public void onCharacteristicRead(
BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic,
int status
) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
}
};
}
重载 broadcastUpdate 函数将特征读取为原来类型(蓝牙属于底层数据传输,所以实际开发更多发送的是16进制数据)。请注意,本节中的数据解析是根据蓝牙心率测量配置文件规范执行的。
private void broadcastUpdate(final String action,
final BluetoothGattCharacteristic characteristic) {
final Intent intent = new Intent(action);
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 {
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);
}
接收 GATT 通知
BLE 应用程序通常会要求设备在特征发生变化时的发送通知。在以下示例中,该服务实现 setCharacteristicNotification() 方法:
class BluetoothService extends Service {
...
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enabled) {
if (bluetoothGatt == null) {
Log.w(TAG, "BluetoothGatt not initialized");
Return;
}
bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
}
}
}
为特征启用通知后,如果远程设备上的特征发生更改,则会触发 onCharacteristicChanged() 回调:
class BluetoothService extends Service {
...
private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
...
@Override
public void onCharacteristicChanged(
BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic
) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
};
}
蓝牙的UUID是什么?有什么用?
UUID(Universally Unique Identifier) 统一表示定义
蓝牙MAC相当于TCP的IP,蓝牙UUID相当于TCP的端口。
蓝牙是通过串口发送 AT 命令,蓝牙默认是在数据模式的,要配置为 AT 命令模式,对其进行设置,不过 UUID 在出厂前是设置过的。
对于蓝牙设备,每个服务都有一个与它对应的UUID(唯一的),如:
SPP devices:00001101-0000-1000-8000-00805F9B34FB 信息同步服务:00001104-0000-1000-8000-00805F9B34FB 文件传输服务:00001106-0000-1000-8000-00805F9B34FB
val deviceNameUUID = UUID.fromString("00002a00-0000-1000-8000-00805f9b34fb")
val appearanceUUID = UUID.fromString("00002a01-0000-1000-8000-00805f9b34fb")
val batteryLevelUUID = UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb")
val modelNumberUUID = UUID.fromString("00002A24-0000-1000-8000-00805f9b34fb")
val serialNumberUUID = UUID.fromString("00002A25-0000-1000-8000-00805f9b34fb")
val firmwareRevisionUUID = UUID.fromString("00002A26-0000-1000-8000-00805f9b34fb")
val hardwareRevisionUUID = UUID.fromString("00002A27-0000-1000-8000-00805f9b34fb")
val softwareRevisionUUID = UUID.fromString("00002A28-0000-1000-8000-00805f9b34fb")
val manufacturerNameUUID = UUID.fromString("00002A29-0000-1000-8000-00805f9b34fb")
蓝牙之数据传输问题
Characteristic 属性
read 和 write。
BLE INTRODUCTION: NOTIFY OR INDICATE
Notify 和 Indicate 有什么区别?
Indicate 需要确认,而 Notify 不需要。
默认情况下,只要您的低功耗蓝牙 (BLE) 设备有新数据要发布,您就无法将数据推送到远程客户端。如果您阅读我之前的文章,您就会知道您需要启用适当的权限并包含一个“客户端特征配置描述符”(CCCD)才能实现。不要忘记,您的远程客户端必须通过 CCCD 订阅该属性才能接收推送的数据。
通常,当人们希望他们的远程客户端在他们的 BLE 设备有新数据时异步接收更新时,他们会推送数据。但是,因为 Notify 是未确认的,所以它是不可靠的,因为您将不知道您的远程客户端是否已收到数据。
为了解决这个问题,让我介绍一下 Indicate 功能。它与 Notify 几乎相同,只是它支持确认。如果您的远程客户端收到数据,则必须发送确认。但是,这种可靠性是以牺牲速度为代价的,因为您的 BLE 设备将等待确认直到超时。
|