Created
April 25, 2025 15:28
-
-
Save nickfox-taterli/3fb4c10d46c09fe172307a51c5aa8c41 to your computer and use it in GitHub Desktop.
Android蓝牙心率演示,配合默认CH579测试程序.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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