Skip to content

Instantly share code, notes, and snippets.

@nickfox-taterli
Created April 25, 2025 15:28
Show Gist options
  • Save nickfox-taterli/3fb4c10d46c09fe172307a51c5aa8c41 to your computer and use it in GitHub Desktop.
Save nickfox-taterli/3fb4c10d46c09fe172307a51c5aa8c41 to your computer and use it in GitHub Desktop.
Android蓝牙心率演示,配合默认CH579测试程序.
package com.example.bleapplication;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.bleapplication.util.PermissionHelper;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.os.Handler;
import java.util.List;
import java.util.UUID;
@SuppressLint("SetTextI18n")
public class MainActivity extends AppCompatActivity {
private static final String TAG = "HeartRateBLE";
private static final String DEVICE_NAME = "Heart Rate Sensor";
private static final int REQUEST_ENABLE_BT = 1;
private static final int PERMISSION_REQUEST_CODE = 2;
private BluetoothAdapter bluetoothAdapter;
private BluetoothLeScanner bluetoothLeScanner;
private boolean scanning;
private Handler handler = new Handler();
private BluetoothGatt bluetoothGatt;
private TextView statusText;
private TextView heartRateValue;
private TextView sensorContactValue;
private TextView energyExpendedValue;
private static final long SCAN_PERIOD = 10000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
statusText = findViewById(R.id.status_text);
heartRateValue = findViewById(R.id.heart_rate_value);
sensorContactValue = findViewById(R.id.sensor_contact_value);
energyExpendedValue = findViewById(R.id.energy_expended_value);
if (PermissionHelper.checkAndRequestPermissions(this)) {
// 所有权限都已授予
startBleApp();
} else {
Toast.makeText(this,"权限申请不正确,应用不会继续执行.",Toast.LENGTH_LONG).show();
}
}
public void updateHeartRateData(int heartRate, String contactStatus, int energyExpended) {
runOnUiThread(() -> {
heartRateValue.setText(heartRate + " bpm");
sensorContactValue.setText(contactStatus);
energyExpendedValue.setText(energyExpended + " kJ");
});
}
private void requestEnableBluetooth() {
// 请求用户开启蓝牙
// 实际应用中应该使用startActivityForResult
Toast.makeText(this, "蓝牙似乎没有开启?", Toast.LENGTH_SHORT).show();
finish();
}
private void startBleApp(){
// 初始化蓝牙适配器
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
bluetoothAdapter = bluetoothManager.getAdapter();
// 检查蓝牙是否开启
if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
requestEnableBluetooth();
}
startBLEScan();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (PermissionHelper.handlePermissionResult(requestCode, permissions, grantResults)) {
// 所有权限都已授予
startBleApp();
} else {
// 有权限被拒绝
String description = PermissionHelper.getDeniedPermissionDescription(this);
Toast.makeText(this,description,Toast.LENGTH_LONG).show();
}
}
private void startBLEScan() {
bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
if (bluetoothLeScanner == null) {
Log.e(TAG, "Unable to get BluetoothLeScanner");
return;
}
statusText.setText("Scanning for " + DEVICE_NAME + "...");
// 停止扫描后延迟一段时间
handler.postDelayed(() -> {
if (scanning) {
scanning = false;
bluetoothLeScanner.stopScan(leScanCallback);
statusText.setText("Scan completed. Device not found.");
}
}, SCAN_PERIOD);
scanning = true;
bluetoothLeScanner.startScan(leScanCallback);
}
// 设备扫描回调
private ScanCallback leScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
BluetoothDevice device = result.getDevice();
String deviceName = device.getName();
if (deviceName != null && deviceName.equals(DEVICE_NAME)) {
Log.i(TAG, "Found device: " + deviceName + " - " + device.getAddress());
scanning = false;
bluetoothLeScanner.stopScan(this);
// 连接到设备
connectToDevice(device);
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
for (ScanResult result : results) {
onScanResult(-1, result); // 使用无效的callbackType
}
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG, "BLE Scan Failed with error code: " + errorCode);
scanning = false;
statusText.setText("Scan failed. Error code: " + errorCode);
}
};
private void connectToDevice(BluetoothDevice device) {
statusText.setText("Connecting to " + device.getName() + "...");
bluetoothGatt = device.connectGatt(this, false, gattCallback);
}
// GATT回调
private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothGatt.STATE_CONNECTED) {
Log.i(TAG, "Connected to GATT server");
runOnUiThread(() -> statusText.setText("Connected to " + DEVICE_NAME));
// 发现服务
gatt.discoverServices();
} else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
Log.i(TAG, "Disconnected from GATT server");
runOnUiThread(() -> statusText.setText("Disconnected from " + DEVICE_NAME));
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
if (characteristic.getUuid().equals(UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb"))) {
// 解析心率数据
int flag = characteristic.getProperties();
int format = (flag & 0x01) == 0x01 ? BluetoothGattCharacteristic.FORMAT_UINT16 : BluetoothGattCharacteristic.FORMAT_UINT8;
int heartRate = characteristic.getIntValue(format, 1);
// 解析传感器接触状态
String contactStatus;
int contactValue = (flag >> 1) & 0x03;
switch (contactValue) {
case 0: contactStatus = "Not supported"; break;
case 1: contactStatus = "No contact"; break;
case 2: contactStatus = "Contact"; break;
default: contactStatus = "Unknown";
}
// 解析能量消耗
int energyExpended = 0;
if ((flag & 0x08) == 0x08) {
energyExpended = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, 2 + (format == BluetoothGattCharacteristic.FORMAT_UINT16 ? 2 : 1));
}
updateHeartRateData(heartRate, contactStatus, energyExpended);
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
super.onServicesDiscovered(gatt, status);
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.i(TAG, "Services discovered");
runOnUiThread(() -> statusText.setText("Services discovered on " + DEVICE_NAME));
// 获取心率服务
BluetoothGattService heartRateService = gatt.getService(UUID.fromString("0000180D-0000-1000-8000-00805f9b34fb"));
if (heartRateService != null) {
// 获取心率测量特征
BluetoothGattCharacteristic heartRateChar = heartRateService.getCharacteristic(
UUID.fromString("00002A37-0000-1000-8000-00805f9b34fb"));
if (heartRateChar != null) {
// 启用特征通知
gatt.setCharacteristicNotification(heartRateChar, true);
// 设置通知描述符
BluetoothGattDescriptor descriptor = heartRateChar.getDescriptor(
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
if (descriptor != null) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
gatt.writeDescriptor(descriptor);
}
} else {
Log.w(TAG, "Heart rate characteristic not found");
runOnUiThread(() -> statusText.setText("Heart rate characteristic not found"));
}
} else {
Log.w(TAG, "Heart rate service not found");
runOnUiThread(() -> statusText.setText("Heart rate service not found"));
}
} else {
Log.w(TAG, "onServicesDiscovered received: " + status);
runOnUiThread(() -> statusText.setText("Service discovery failed: " + status));
}
}
};
@Override
protected void onDestroy() {
super.onDestroy();
if (bluetoothGatt != null) {
bluetoothGatt.close();
bluetoothGatt = null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment